From 1ba505f280aae4f01611cf260442c2522d3493cc Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Wed, 21 Aug 2024 15:01:24 +0200 Subject: [PATCH 01/35] Implemented SPARQL in model index functionalities. --- .../model_index_sparql_integration.ipynb | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 notebooks/model_index_sparql_integration.ipynb diff --git a/notebooks/model_index_sparql_integration.ipynb b/notebooks/model_index_sparql_integration.ipynb new file mode 100644 index 0000000..856b897 --- /dev/null +++ b/notebooks/model_index_sparql_integration.ipynb @@ -0,0 +1,301 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the required packages and libraries\n", + "import json\n", + "import requests\n", + "import datetime\n", + "import pandas as pd\n", + "import os\n", + "from dotenv import load_dotenv \n", + "from pathlib import Path\n", + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pyprediktormapclient.auth_client import AUTH_CLIENT\n", + "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", + "from pyprediktormapclient.shared import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", + "dotenv_path = Path(\"../.env\")\n", + "load_dotenv(dotenv_path=dotenv_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "username = os.environ[\"USERNAME\"]\n", + "password = os.environ[\"PASSWORD\"]\n", + "opcua_rest_url = os.environ[\"OPC_UA_REST_URL\"]\n", + "opcua_server_url = os.environ[\"OPC_UA_SERVER_URL\"]\n", + "model_index_url = os.environ[\"MODEL_INDEX_URL\"]\n", + "ory_url = os.environ[\"ORY_URL\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Getting ory bearer token\n", + "auth_client = AUTH_CLIENT(rest_url=ory_url, username=username, password=password)\n", + "auth_client.request_new_ory_token()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = model_index_url\n", + "headers = {\"Content-Type\": \"application/json\",\n", + " \"Accept\": \"application/json\"\n", + " }\n", + "auth_client = auth_client\n", + "session: requests.Session = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if auth_client is not None:\n", + " if auth_client.token is not None:\n", + " headers[\"Authorization\"] = f\"Bearer {auth_client.token.session_token}\"\n", + " if hasattr(auth_client, 'session_token'):\n", + " headers[\"Cookie\"] = f\"ory_kratos_session={auth_client.session_token}\"\n", + "\n", + "body = {\"Connection\": {\"Url\": url, \"AuthenticationType\": 1}}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_object_types() -> str:\n", + " content = request_from_api(url, \"GET\", \"query/object-types\", headers=headers, session=session)\n", + "\n", + " return content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "object_types = get_object_types()\n", + "object_types" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_namespace_array() -> str:\n", + " \"\"\"Get the namespace array\n", + "\n", + " Returns:\n", + " str: the JSON returned from the server\n", + " \"\"\"\n", + " content = request_from_api(url, \"GET\", \"query/namespaces\", headers=headers, session=session)\n", + "\n", + " return content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "name_space_array = get_namespace_array()\n", + "name_space_array" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_object_type_id_from_name(type_name: str) -> str:\n", + " \"\"\"Get object type id from type name\n", + "\n", + " Args:\n", + " type_name (str): type name\n", + "\n", + " Returns:\n", + " str: the type id that corresponds with the id (None if not found)\n", + " \"\"\"\n", + " try:\n", + " obj_type = next(\n", + " item for item in object_types if item[\"BrowseName\"][\"Name\"] == type_name\n", + " )\n", + " except StopIteration:\n", + " obj_type = {}\n", + "\n", + " if obj_type:\n", + " node_id = obj_type.get(\"NodeId\", {})\n", + " object_type_id = f'{node_id.get(\"Namespace\")}:{node_id.get(\"IdType\")}:{node_id.get(\"Id\")}'\n", + " else:\n", + " object_type_id = None\n", + "\n", + " return object_type_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "object_type_id = get_object_type_id_from_name(\"StringSetType\")\n", + "object_type_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_objects_of_type(type_name: str) -> str:\n", + " \"\"\"Function to get all the types of an object\n", + "\n", + " Args:\n", + " type_name (str): type name\n", + "\n", + " Returns:\n", + " A json-formatted string with the objects (or None if the type is not found)\n", + " \"\"\"\n", + " object_type_id = get_object_type_id_from_name(type_name)\n", + " if object_type_id is None:\n", + " return None\n", + " \n", + " namespace, id_type, id_ = object_type_id.split(':')\n", + "\n", + " body = json.dumps({\"Id\": int(id_), \"IdType\": int(id_type), \"Namespace\": int(namespace)})\n", + " content = request_from_api(url, \"POST\", \"query/objects-of-type-with-variables-and-properties\", body, headers=headers, session=session)\n", + "\n", + " return content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "objects_of_type = get_objects_of_type(\"SiteType\")\n", + "objects_of_type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_object_descendants(\n", + " type_name: str,\n", + " ids: list,\n", + " domain: str,\n", + " ) -> str:\n", + " \"\"\"A function to get object descendants\n", + "\n", + " Args:\n", + " type_name (str): type_name of a descendant\n", + " ids (list): a list of ids you want the descendants for\n", + " domain (str): PV_Assets or PV_Serves\n", + "\n", + " Returns:\n", + " A json-formatted string with descendats data of selected object (or None if the type is not found)\n", + " \"\"\"\n", + " if type_name is None or not ids:\n", + " raise ValidationError(\"type_name and ids cannot be None or empty\")\n", + " \n", + " id = get_object_type_id_from_name(type_name)\n", + " body = json.dumps(\n", + " {\n", + " \"typeId\": id,\n", + " \"objectIds\": ids,\n", + " \"domain\": domain,\n", + " }\n", + " )\n", + " content = request_from_api(url, \"POST\", \"query/object-descendants?include_members=true\", body, headers=headers, session=session)\n", + "\n", + " return content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "object_descendants = get_object_descendants(\"StringSetType\", ['3:1:Enterprise.JO-GL'], \"PV_Assets\")\n", + "object_descendants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv_map_client", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 2c2e23b1428d8443fd4b70b27d06617ac547d7b3 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Tue, 27 Aug 2024 14:52:26 +0200 Subject: [PATCH 02/35] Implemented CI/CD pipeline and updated code to pass pre-commit hooks. --- .github/workflows/ci-cd-webhook.yml | 28 + .github/workflows/codeql-analysis.yml | 6 +- .github/workflows/pages.yml | 2 +- .github/workflows/tox.yml | 2 +- .gitignore | 2 +- .pre-commit-config.yaml | 39 ++ docs/conf.py | 9 +- .../get_and write_historical_opc_ua_values.py | 59 +- examples/get_and write_opc_ua_values.py | 35 +- .../get_historical_opc_ua_values_with_auth.py | 35 +- pyproject.toml | 2 +- requirements.txt | 11 +- setup.cfg | 2 +- setup.py | 12 +- src/pyprediktormapclient/__init__.py | 14 +- src/pyprediktormapclient/analytics_helper.py | 88 +-- src/pyprediktormapclient/auth_client.py | 69 ++- src/pyprediktormapclient/dwh/__init__.py | 1 - .../dwh/context/enercast.py | 12 +- src/pyprediktormapclient/dwh/context/plant.py | 2 +- .../dwh/context/solcast.py | 4 +- src/pyprediktormapclient/dwh/db.py | 46 +- src/pyprediktormapclient/dwh/dwh.py | 25 +- src/pyprediktormapclient/model_index.py | 112 ++-- src/pyprediktormapclient/opc_ua.py | 372 ++++++++----- src/pyprediktormapclient/shared.py | 29 +- ...ics_helper.py => analytics_helper_test.py} | 12 +- tests/auth_client_test.py | 464 ++++++++++++++++ tests/conftest.py | 11 +- .../{test_enercast.py => enercast_test.py} | 24 +- .../context/{test_plant.py => plant_test.py} | 56 +- .../{test_solcast.py => solcast_test.py} | 2 +- tests/dwh/{test_db.py => db_test.py} | 79 ++- tests/dwh/{test_dwh.py => dwh_test.py} | 47 +- ...est_model_index.py => model_index_test.py} | 17 +- tests/{test_opc_ua.py => opc_ua_test.py} | 525 +++++++++--------- tests/{test_shared.py => shared_test.py} | 19 +- tests/test_auth_client.py | 427 -------------- tox.ini | 16 +- 39 files changed, 1609 insertions(+), 1108 deletions(-) create mode 100644 .github/workflows/ci-cd-webhook.yml create mode 100644 .pre-commit-config.yaml rename tests/{test_analytics_helper.py => analytics_helper_test.py} (96%) create mode 100644 tests/auth_client_test.py rename tests/dwh/context/{test_enercast.py => enercast_test.py} (81%) rename tests/dwh/context/{test_plant.py => plant_test.py} (72%) rename tests/dwh/context/{test_solcast.py => solcast_test.py} (98%) rename tests/dwh/{test_db.py => db_test.py} (92%) rename tests/dwh/{test_dwh.py => dwh_test.py} (87%) rename tests/{test_model_index.py => model_index_test.py} (94%) rename tests/{test_opc_ua.py => opc_ua_test.py} (70%) rename tests/{test_shared.py => shared_test.py} (80%) delete mode 100644 tests/test_auth_client.py diff --git a/.github/workflows/ci-cd-webhook.yml b/.github/workflows/ci-cd-webhook.yml new file mode 100644 index 0000000..978da6c --- /dev/null +++ b/.github/workflows/ci-cd-webhook.yml @@ -0,0 +1,28 @@ +name: CI/CD and Webhook + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Create virtual environment + run: python -m venv .venv + + - name: Activate virtual environment + run: source .venv/bin/activate + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run pre-commit checks + run: pre-commit run --all-files diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 85248d9..3b0f857 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,11 +48,11 @@ jobs: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild @@ -61,7 +61,7 @@ jobs: # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. + # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 932885d..124ef3b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -21,4 +21,4 @@ jobs: uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} - branch: gh-pages \ No newline at end of file + branch: gh-pages diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 46128a5..ade6c97 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -77,4 +77,4 @@ jobs: - name: Test with tox run: | - tox \ No newline at end of file + tox diff --git a/.gitignore b/.gitignore index b8f4062..45e0e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,4 @@ venv*/ # data folders *data*/ *json -*parquet \ No newline at end of file +*parquet diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..264e773 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +default_language_version: + python: python3.12 + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: requirements-txt-fixer + - id: debug-statements + - id: name-tests-test +- repo: https://github.com/PyCQA/docformatter + rev: v1.7.5 + hooks: + - id: docformatter + args: ["--in-place"] +- repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 + hooks: + - id: autoflake + args: ["--remove-all-unused-imports", "--in-place"] +- repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + args: ["--line-length", "79"] +- repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + args: ["--ignore=E501,W503"] +- repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: ["-x", "tests/*"] diff --git a/docs/conf.py b/docs/conf.py index 151ecb0..61cfbeb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,6 @@ import os import sys import shutil -import sphinx_rtd_theme # -- Path setup -------------------------------------------------------------- @@ -44,7 +43,9 @@ try: import sphinx - cmd_line = f"sphinx-apidoc --implicit-namespaces -f -o {output_dir} {module_dir}" + cmd_line = ( + f"sphinx-apidoc --implicit-namespaces -f -o {output_dir} {module_dir}" + ) args = cmd_line.split(" ") if tuple(sphinx.__version__.split(".")) >= ("1", "7"): @@ -106,7 +107,9 @@ version = "" if not version or version.lower() == "unknown": - version = os.getenv("READTHEDOCS_VERSION", "unknown") # automatically set by RTD + version = os.getenv( + "READTHEDOCS_VERSION", "unknown" + ) # automatically set by RTD release = version diff --git a/examples/get_and write_historical_opc_ua_values.py b/examples/get_and write_historical_opc_ua_values.py index d0813e0..094b2db 100644 --- a/examples/get_and write_historical_opc_ua_values.py +++ b/examples/get_and write_historical_opc_ua_values.py @@ -2,7 +2,13 @@ import datetime # Import OPC UA functions -from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteHistoricalVariables, Value, SubValue +from pyprediktormapclient.opc_ua import ( + OPC_UA, + Variables, + WriteHistoricalVariables, + Value, + SubValue, +) def main(): @@ -12,9 +18,17 @@ def main(): opcua_server_url = "opc.tcp://10.100.59.219:4853" # Initate the OPC UA API with a fixed namespace list - tsdata = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=namespace_list) - variable_1 = Variables(Id='SSO.JO-GL.Signals.Weather.Albedo', Namespace=5, IdType=1) - variable_2 = Variables(Id='SSO.EG-AS.Signals.Weather.Albedo', Namespace=3, IdType=1) + tsdata = OPC_UA( + rest_url=opcua_rest_url, + opcua_url=opcua_server_url, + namespaces=namespace_list, + ) + variable_1 = Variables( + Id="SSO.JO-GL.Signals.Weather.Albedo", Namespace=5, IdType=1 + ) + variable_2 = Variables( + Id="SSO.EG-AS.Signals.Weather.Albedo", Namespace=3, IdType=1 + ) variables = [variable_1, variable_2] live_value = tsdata.get_historical_aggregated_values( @@ -22,20 +36,39 @@ def main(): end_time=(datetime.datetime.now()), pro_interval=3600000, agg_name="Average", - variable_list=variables + variable_list=variables, ) print(live_value) - value_1_1 = Value(Value=SubValue(Type=10, Body=1.1), SourceTimestamp=datetime.datetime.now() - datetime.timedelta(1)) - value_1_2 = Value(Value=SubValue(Type=10, Body=2.1), SourceTimestamp=datetime.datetime.now()) + value_1_1 = Value( + Value=SubValue(Type=10, Body=1.1), + SourceTimestamp=datetime.datetime.now() - datetime.timedelta(1), + ) + value_1_2 = Value( + Value=SubValue(Type=10, Body=2.1), + SourceTimestamp=datetime.datetime.now(), + ) update_values_1 = [value_1_1, value_1_2] - value_2_1 = Value(Value=SubValue(Type=10, Body=11.1), SourceTimestamp="2022-11-01T12:00:00") - value_2_2 = Value(Value=SubValue(Type=10, Body=22.1), SourceTimestamp="2022-11-01T13:00:00") + value_2_1 = Value( + Value=SubValue(Type=10, Body=11.1), + SourceTimestamp="2022-11-01T12:00:00", + ) + value_2_2 = Value( + Value=SubValue(Type=10, Body=22.1), + SourceTimestamp="2022-11-01T13:00:00", + ) update_values_2 = [value_2_1, value_2_2] - write_variable_1 = WriteHistoricalVariables(NodeId=variable_1, PerformInsertReplace=1, UpdateValues=update_values_1) - write_variable_2 = WriteHistoricalVariables(NodeId=variable_2, PerformInsertReplace=1, UpdateValues=update_values_2) - write_historical_data = tsdata.write_historical_values([write_variable_1, write_variable_2]) + write_variable_1 = WriteHistoricalVariables( + NodeId=variable_1, PerformInsertReplace=1, UpdateValues=update_values_1 + ) + write_variable_2 = WriteHistoricalVariables( + NodeId=variable_2, PerformInsertReplace=1, UpdateValues=update_values_2 + ) + write_historical_data = tsdata.write_historical_values( + [write_variable_1, write_variable_2] + ) print(write_historical_data) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/get_and write_opc_ua_values.py b/examples/get_and write_opc_ua_values.py index 4915a1f..c0077e9 100644 --- a/examples/get_and write_opc_ua_values.py +++ b/examples/get_and write_opc_ua_values.py @@ -1,6 +1,11 @@ # Import OPC UA functions -from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, Value, SubValue - +from pyprediktormapclient.opc_ua import ( + OPC_UA, + Variables, + WriteVariables, + Value, + SubValue, +) def main(): @@ -10,17 +15,24 @@ def main(): opcua_server_url = "opc.tcp://10.100.59.219:4853" # Initate the OPC UA API with a fixed namespace list - tsdata = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=namespace_list) + tsdata = OPC_UA( + rest_url=opcua_rest_url, + opcua_url=opcua_server_url, + namespaces=namespace_list, + ) # Setup variables to fetch - variable_1 = Variables(Id='SSO.JO-GL.Signals.Weather.Albedo', Namespace=5, IdType=1) - variable_2 = Variables(Id='SSO.EG-AS.Signals.Weather.Albedo', Namespace=3, IdType=1) + variable_1 = Variables( + Id="SSO.JO-GL.Signals.Weather.Albedo", Namespace=5, IdType=1 + ) + variable_2 = Variables( + Id="SSO.EG-AS.Signals.Weather.Albedo", Namespace=3, IdType=1 + ) variables = [variable_1, variable_2] print(variables) live_values = tsdata.get_values(variables) print(live_values) - # Example write using json. # write_values = tsdata.write_values( # [ @@ -44,12 +56,17 @@ def main(): # ] # ) - #Example using classes from OPC UA class, writing to the same variables fetched above. + # Example using classes from OPC UA class, writing to the same variables fetched above. sub_value = SubValue(Type=10, Body="3.3") - values = Value(Value=sub_value, SourceTimestamp="2022-01-01T12:00:00Z", ServerTimestamp="2022-01-01T12:00:00Z") + values = Value( + Value=sub_value, + SourceTimestamp="2022-01-01T12:00:00Z", + ServerTimestamp="2022-01-01T12:00:00Z", + ) write_variables = WriteVariables(NodeId=variable_1, Value=values) write_values = tsdata.write_values([write_variables]) print(write_values) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/get_historical_opc_ua_values_with_auth.py b/examples/get_historical_opc_ua_values_with_auth.py index 9beda1f..9be80d9 100644 --- a/examples/get_historical_opc_ua_values_with_auth.py +++ b/examples/get_historical_opc_ua_values_with_auth.py @@ -1,30 +1,41 @@ # Import the required packeages import datetime import json -from urllib.parse import urlparse + # Import OPC UA functions from pyprediktormapclient.opc_ua import OPC_UA, Variables from pyprediktormapclient.auth_client import AUTH_CLIENT + def main(): namespace_list = [] # Read credentials and server url from secrets file - f = open ('src/pyprediktormapclient/secrets.cfg', "r") + f = open("src/pyprediktormapclient/secrets.cfg", "r") # Reading from secret file with credentials api_config = json.loads(f.read()) - - opcua_rest_url = api_config.get('opcua_rest_url') - opcua_server_url = api_config.get('opcua_server_url') - ory_url = api_config.get('ory_url') - auth_client = AUTH_CLIENT(rest_url=ory_url, username=api_config.get("username"), password=api_config.get("password")) - auth_client.request_new_ory_token() + opcua_rest_url = api_config.get("opcua_rest_url") + opcua_server_url = api_config.get("opcua_server_url") + ory_url = api_config.get("ory_url") + auth_client = AUTH_CLIENT( + rest_url=ory_url, + username=api_config.get("username"), + password=api_config.get("password"), + ) + auth_client.request_new_ory_token() # Initate the OPC UA API with a fixed namespace list - tsdata = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=namespace_list, auth_client=auth_client) - variable_1 = Variables(Id='V|ZA-HE-SWS-QoS.ActivePower', Namespace=2, IdType=1) + tsdata = OPC_UA( + rest_url=opcua_rest_url, + opcua_url=opcua_server_url, + namespaces=namespace_list, + auth_client=auth_client, + ) + variable_1 = Variables( + Id="V|ZA-HE-SWS-QoS.ActivePower", Namespace=2, IdType=1 + ) variables = [variable_1] # Live value data of trackers live_value = tsdata.get_historical_aggregated_values( @@ -32,10 +43,10 @@ def main(): end_time=(datetime.datetime.now()), pro_interval=3600000, agg_name="Average", - variable_list=variables + variable_list=variables, ) print(live_value) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pyproject.toml b/pyproject.toml index 776de00..2a7edc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,4 +11,4 @@ version_scheme = "no-guess-dev" [tool.pytest.ini_options] pythonpath = [ "src" -] \ No newline at end of file +] diff --git a/requirements.txt b/requirements.txt index 1a91227..d2d0d5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ +-e . +ipykernel nest_asyncio +pre-commit +pytest-asyncio +python-dotenv sphinx_rtd_theme +tenacity tox -ipykernel tqdm -tenacity -pytest-asyncio -python-dotenv --e . \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index cc107ee..b41de53 100644 --- a/setup.cfg +++ b/setup.cfg @@ -128,4 +128,4 @@ envlist = py39, py310, py311, mypy python = 3.9: py39 3.10: py310, mypy - 3.11: py311 \ No newline at end of file + 3.11: py311 diff --git a/setup.py b/setup.py index bde5aaf..6b2f2c5 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ -""" - Setup file for pyPrediktorMapClient. - Use setup.cfg to configure your project. +"""Setup file for pyPrediktorMapClient. Use setup.cfg to configure your +project. - This file was generated with PyScaffold 4.3. - PyScaffold helps you to put up the scaffold of your new Python project. - Learn more under: https://pyscaffold.org/ +This file was generated with PyScaffold 4.3. +PyScaffold helps you to put up the scaffold of your new Python project. +Learn more under: https://pyscaffold.org/ """ + from setuptools import setup if __name__ == "__main__": diff --git a/src/pyprediktormapclient/__init__.py b/src/pyprediktormapclient/__init__.py index 6df284e..19db91b 100644 --- a/src/pyprediktormapclient/__init__.py +++ b/src/pyprediktormapclient/__init__.py @@ -1,14 +1,16 @@ import sys -from .shared import * -from .analytics_helper import * -from .model_index import * -from .opc_ua import * if sys.version_info[:2] >= (3, 8): # TODO: Import directly (no need for conditional) when `python_requires = >= 3.9` - from importlib.metadata import PackageNotFoundError, version # pragma: no cover + from importlib.metadata import ( + PackageNotFoundError, + version, + ) # pragma: no cover else: - from importlib_metadata import PackageNotFoundError, version # pragma: no cover + from importlib_metadata import ( + PackageNotFoundError, + version, + ) # pragma: no cover try: # Change here if project is renamed and does not equal the package name diff --git a/src/pyprediktormapclient/analytics_helper.py b/src/pyprediktormapclient/analytics_helper.py index 569200f..2bb6b88 100644 --- a/src/pyprediktormapclient/analytics_helper.py +++ b/src/pyprediktormapclient/analytics_helper.py @@ -1,7 +1,6 @@ import pandas as pd -import json import logging -from typing import List +from typing import List, Any from pydantic import validate_call, constr @@ -10,10 +9,10 @@ class AnalyticsHelper: - """Your data as a Pandas DataFrame, but with a specific formatting - and some nifty functions. Put the data from ModelIndex in here - and move further on by adding data and from other ModelIndex calls - or live or historical data from OPC UA. + """Your data as a Pandas DataFrame, but with a specific formatting and some + nifty functions. Put the data from ModelIndex in here and move further on + by adding data and from other ModelIndex calls or live or historical data + from OPC UA. Columns in the normalizes dataframe are: @@ -29,10 +28,10 @@ class AnalyticsHelper: Args: input (List): The return from a ModelIndex call function - + Attributes: dataframe (pandas.DataFrame): The normalized dataframe - + Returns: An instance of the class with some resources and attributes @@ -42,7 +41,9 @@ class AnalyticsHelper: @validate_call def __init__(self, input: List): - self.dataframe = pd.DataFrame(input) # Create a DataFrame from the input + self.dataframe = pd.DataFrame( + input + ) # Create a DataFrame from the input self.normalize() def normalize(self): @@ -68,7 +69,9 @@ def normalize(self): # Check if the content is from get_objects_of_type if "DisplayName" in self.dataframe.columns: - self.dataframe.rename(columns={"DisplayName": "Name"}, inplace=True) + self.dataframe.rename( + columns={"DisplayName": "Name"}, inplace=True + ) # Check if the content is from object-descendants if "DescendantId" in self.dataframe.columns: @@ -94,14 +97,15 @@ def normalize(self): # Now check to see if all needed columns are there, else set to None for required_key in ["Id", "Name", "Vars", "Props"]: - if not required_key in self.dataframe: + if required_key not in self.dataframe: self.dataframe = None return @validate_call def namespaces_as_list(self, list_of_dicts: List) -> List: - """Takes the output of a get_namespace_array() request from ModelIndex and - generates a list of strings that can be used for the OPC UA Values API + """Takes the output of a get_namespace_array() request from ModelIndex + and generates a list of strings that can be used for the OPC UA Values + API. Args: list_of_dicts (List): A list in of dicts like [{'Idx': 0, 'Uri': 'http://opcfoundation.org/UA/'}, etc] @@ -117,7 +121,11 @@ def namespaces_as_list(self, list_of_dicts: List) -> List: return new_list @validate_call - def split_id(self, id: constr(pattern=r"^\d+:\d+:\S+$")): + def split_id(self, id: Any) -> dict: + + if not isinstance(id, constr(pattern=r"^\d+:\d+:\S+$")): + raise ValueError("Invalid id format") + id_split = id.split(":") return { "Id": id_split[2], @@ -126,7 +134,7 @@ def split_id(self, id: constr(pattern=r"^\d+:\d+:\S+$")): } def list_of_ids(self) -> list: - """Extracts the values in the column "Id" to a list of unique IDs + """Extracts the values in the column "Id" to a list of unique IDs. Returns: list: Unique IDs @@ -141,7 +149,7 @@ def list_of_ids(self) -> list: return list(set(self.dataframe["Id"].to_list())) def list_of_names(self) -> list: - """Extracts the values in the column "Name" to a list of unique names + """Extracts the values in the column "Name" to a list of unique names. Returns: list: Unique names @@ -156,7 +164,7 @@ def list_of_names(self) -> list: return list(set(self.dataframe["Name"].to_list())) def list_of_types(self) -> list: - """Extracts the values in the column "Type" to a list of unique types + """Extracts the values in the column "Type" to a list of unique types. Returns: list: Unique types @@ -172,7 +180,7 @@ def list_of_types(self) -> list: def list_of_variable_names(self) -> list: """Explodes the content of the column "Vars" and extracts the values - from DisplayName into a list of unique values + from DisplayName into a list of unique values. Returns: list: Unique variable names @@ -183,7 +191,7 @@ def list_of_variable_names(self) -> list: return [] # Check that the Vars column contains pd.Series content - if not type(self.dataframe["Vars"][0]) == list: + if not isinstance(self.dataframe["Vars"][0], list): return [] vars_set = set([]) @@ -195,7 +203,8 @@ def list_of_variable_names(self) -> list: return list(vars_set) def properties_as_dataframe(self) -> pd.DataFrame: - """Explodes the column "Props" into a new dataframe. Column names will be + """Explodes the column "Props" into a new dataframe. Column names will + be. - Id (same as from the original dataframe) - Name (same as from the original dataframe) @@ -212,7 +221,7 @@ def properties_as_dataframe(self) -> pd.DataFrame: return None # Check if the Props column contains pd.Series data - if not type(self.dataframe["Props"][0]) == list: + if not isinstance(self.dataframe["Props"][0], list): return None # Explode will add a row for every series in the Prop column @@ -224,11 +233,11 @@ def properties_as_dataframe(self) -> pd.DataFrame: propery_frame["Value"] = "" # Add new columns - propery_frame[['Property', 'Value']] =\ - propery_frame['Props'].apply(lambda x: pd.Series({ - 'Property': x['DisplayName'], - 'Value': x['DisplayName'] - })) + propery_frame[["Property", "Value"]] = propery_frame["Props"].apply( + lambda x: pd.Series( + {"Property": x["DisplayName"], "Value": x["DisplayName"]} + ) + ) # Remove original Props column propery_frame.drop(columns=["Props"], inplace=True) @@ -236,7 +245,8 @@ def properties_as_dataframe(self) -> pd.DataFrame: return propery_frame def variables_as_dataframe(self) -> pd.DataFrame: - """Explodes the column "Vars" into a new dataframe. Column names will be + """Explodes the column "Vars" into a new dataframe. Column names will + be. - Id (same as from the original dataframe) - Name (same as from the original dataframe) @@ -253,7 +263,7 @@ def variables_as_dataframe(self) -> pd.DataFrame: return None # Check if the Vars column contains list content - if not type(self.dataframe["Vars"][0]) == list: + if not isinstance(self.dataframe["Vars"][0], list): return None # Explode will add a row for every series in the Prop column @@ -262,12 +272,17 @@ def variables_as_dataframe(self) -> pd.DataFrame: variables_frame.drop(columns=["Props"], inplace=True) # Add new columns - variables_frame[['VariableId', 'VariableName', 'VariableIdSplit']] =\ - variables_frame['Vars'].apply(lambda x: pd.Series({ - 'VariableId': x['Id'], - 'VariableName': x['DisplayName'], - 'VariableIdSplit': self.split_id(x['Id']) - })) + variables_frame[ + ["VariableId", "VariableName", "VariableIdSplit"] + ] = variables_frame["Vars"].apply( + lambda x: pd.Series( + { + "VariableId": x["Id"], + "VariableName": x["DisplayName"], + "VariableIdSplit": self.split_id(x["Id"]), + } + ) + ) # Remove the original Vars column variables_frame.drop(columns=["Vars"], inplace=True) @@ -275,8 +290,9 @@ def variables_as_dataframe(self) -> pd.DataFrame: return variables_frame def variables_as_list(self, include_only: List = []) -> List: - """Extracts variables as a list. If there are names listed in the include_only - argument, only variables matching that name will be encluded. + """Extracts variables as a list. If there are names listed in the + include_only argument, only variables matching that name will be + encluded. Args: include_only (list): A list of variable names (str) that should be included diff --git a/src/pyprediktormapclient/auth_client.py b/src/pyprediktormapclient/auth_client.py index b68ddc4..fbd0eed 100644 --- a/src/pyprediktormapclient/auth_client.py +++ b/src/pyprediktormapclient/auth_client.py @@ -1,31 +1,37 @@ -from pydantic import BaseModel, AnyUrl, validate_call, AwareDatetime, field_validator +from pydantic import BaseModel, AnyUrl, AwareDatetime, field_validator from pyprediktormapclient.shared import request_from_api import datetime import requests import json import re + class Ory_Login_Structure(BaseModel): method: str identifier: str password: str + class Token(BaseModel): session_token: str expires_at: AwareDatetime = None - - @field_validator('expires_at', mode='before') + + @field_validator("expires_at", mode="before") def remove_nanoseconds(cls, v): if v is None: return v - match = re.match(r"(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d).\d+(\S+)", v) + match = re.match( + r"(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d).\d+(\S+)", v + ) if match: - return datetime.datetime.strptime(match.group(0)[:-4] + match.group(7), "%Y-%m-%dT%H:%M:%S.%f%z") + return datetime.datetime.strptime( + match.group(0)[:-4] + match.group(7), "%Y-%m-%dT%H:%M:%S.%f%z" + ) return v - + class AUTH_CLIENT: - """Helper functions to authenticate with Ory + """Helper functions to authenticate with Ory. Args: rest_url (str): The complete url of the OPC UA Values REST API. E.g. "http://127.0.0.1:13371/" @@ -34,11 +40,10 @@ class AUTH_CLIENT: Returns: Object - """ - + def __init__(self, rest_url: AnyUrl, username: str, password: str): - """Class initializer + """Class initializer. Args: rest_url (str): The complete url of the Ory server. E.g. "http://127.0.0.1:9099/" @@ -55,10 +60,8 @@ def __init__(self, rest_url: AnyUrl, username: str, password: str): self.headers = {"Content-Type": "application/json"} self.session = requests.Session() - def get_login_id(self) -> None: - """Request login token from Ory - """ + """Request login token from Ory.""" content = request_from_api( rest_url=self.rest_url, method="GET", @@ -70,18 +73,22 @@ def get_login_id(self) -> None: # Handle the error appropriately raise RuntimeError(content["error"]) - if content.get("Success") is False or not isinstance(content.get("id"), str): - error_message = content.get("ErrorMessage", "Unknown error occurred during login.") + if content.get("Success") is False or not isinstance( + content.get("id"), str + ): + error_message = content.get( + "ErrorMessage", "Unknown error occurred during login." + ) raise RuntimeError(error_message) self.id = content.get("id") - def get_login_token(self) -> None: - """Request login token from Ory - """ + """Request login token from Ory.""" params = {"flow": self.id} - body = (Ory_Login_Structure(method="password", identifier=self.username, password=self.password).model_dump()) + body = Ory_Login_Structure( + method="password", identifier=self.username, password=self.password + ).model_dump() content = request_from_api( rest_url=self.rest_url, method="POST", @@ -94,7 +101,7 @@ def get_login_token(self) -> None: if content.get("Success") is False: raise RuntimeError(content.get("ErrorMessage")) - + # Return if no content from server if not isinstance(content.get("session_token"), str): raise RuntimeError(content.get("ErrorMessage")) @@ -103,25 +110,29 @@ def get_login_token(self) -> None: # Check if token has expiry date, save it if it does if isinstance(content.get("session").get("expires_at"), str): # String returned from ory has to many chars in microsec. Remove them - #from_string = content.get("session").get("expires_at") - #date_object = datetime.datetime.strptime(f"{from_string[:-11]}.+00:00", "%Y-%m-%dT%H:%M:%S.%z") + # from_string = content.get("session").get("expires_at") + # date_object = datetime.datetime.strptime(f"{from_string[:-11]}.+00:00", "%Y-%m-%dT%H:%M:%S.%z") try: - self.token = Token(session_token=self.token.session_token, expires_at=content.get("session").get("expires_at")) + self.token = Token( + session_token=self.token.session_token, + expires_at=content.get("session").get("expires_at"), + ) except Exception: # If string returned from Ory cant be parsed, still should be possible to use Ory, # might be a setting in Ory to not return expiry date self.token = Token(session_token=self.token.session_token) def check_if_token_has_expired(self) -> bool: - """Check if token has expired - """ + """Check if token has expired.""" if self.token is None or self.token.expires_at is None: return True - return datetime.datetime.now(datetime.timezone.utc) > self.token.expires_at + return ( + datetime.datetime.now(datetime.timezone.utc) + > self.token.expires_at + ) def request_new_ory_token(self) -> None: - """Request Ory token - """ + """Request Ory token.""" self.get_login_id() - self.get_login_token() \ No newline at end of file + self.get_login_token() diff --git a/src/pyprediktormapclient/dwh/__init__.py b/src/pyprediktormapclient/dwh/__init__.py index 4efe61d..e69de29 100644 --- a/src/pyprediktormapclient/dwh/__init__.py +++ b/src/pyprediktormapclient/dwh/__init__.py @@ -1 +0,0 @@ -from .dwh import DWH diff --git a/src/pyprediktormapclient/dwh/context/enercast.py b/src/pyprediktormapclient/dwh/context/enercast.py index 2486bec..7092f97 100644 --- a/src/pyprediktormapclient/dwh/context/enercast.py +++ b/src/pyprediktormapclient/dwh/context/enercast.py @@ -21,9 +21,15 @@ def get_live_meter_data(self, asset_name: str) -> List: @validate_call def upsert_forecast_data( - self, enercast_forecast_data: Dict, forecast_type_key: Union[int, None] = None + self, + enercast_forecast_data: Dict, + forecast_type_key: Union[int, None] = None, ) -> List: - enercast_forecast_data_json = json.dumps({"results": enercast_forecast_data}) + enercast_forecast_data_json = json.dumps( + {"results": enercast_forecast_data} + ) query = "EXEC dwetl.UpsertEnercastForecastData ?, ?" - return self.dwh.execute(query, enercast_forecast_data_json, forecast_type_key) + return self.dwh.execute( + query, enercast_forecast_data_json, forecast_type_key + ) diff --git a/src/pyprediktormapclient/dwh/context/plant.py b/src/pyprediktormapclient/dwh/context/plant.py index 4783fa0..c7dd85d 100644 --- a/src/pyprediktormapclient/dwh/context/plant.py +++ b/src/pyprediktormapclient/dwh/context/plant.py @@ -13,7 +13,7 @@ def __init__(self, dwh: IDWH) -> None: def get_optimal_tracker_angles(self, facility_name: str) -> List: query = ( f"SET NOCOUNT ON; EXEC dwetl.GetOptimalTrackerAngleParameters " - + f"@FacilityName = N'{facility_name}'" + f"@FacilityName = N'{facility_name}'" ) return self.dwh.fetch(query) diff --git a/src/pyprediktormapclient/dwh/context/solcast.py b/src/pyprediktormapclient/dwh/context/solcast.py index c6504ac..bdbc597 100644 --- a/src/pyprediktormapclient/dwh/context/solcast.py +++ b/src/pyprediktormapclient/dwh/context/solcast.py @@ -31,4 +31,6 @@ def upsert_forecast_data( ) query = "EXEC dwetl.UpsertSolcastForecastData ?, ?" - return self.dwh.execute(query, solcast_forecast_data_json, forecast_type_key) + return self.dwh.execute( + query, solcast_forecast_data_json, forecast_type_key + ) diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index 18afee9..c78d59a 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -109,7 +109,9 @@ def fetch(self, query: str, to_dataframe: bool = False) -> List[Any]: {name: row[index] for index, name in enumerate(columns)} ) - data_sets.append(pd.DataFrame(data_set) if to_dataframe else data_set) + data_sets.append( + pd.DataFrame(data_set) if to_dataframe else data_set + ) if not self.cursor.nextset(): break @@ -146,8 +148,8 @@ def execute(self, query: str, *args, **kwargs) -> List[Any]: result = [] try: result = self.cursor.fetchall() - except Exception: - pass + except Exception as e: + logging.error(f"Failed to fetch results: {e}") self.__commit() @@ -167,17 +169,23 @@ def __set_driver(self, driver_index: int) -> None: the driver for you. """ if driver_index < 0: - self.driver = self.__get_list_of_available_and_supported_pyodbc_drivers()[0] + self.driver = ( + self.__get_list_of_available_and_supported_pyodbc_drivers()[0] + ) return - if self.__get_number_of_available_pyodbc_drivers() < (driver_index + 1): + if self.__get_number_of_available_pyodbc_drivers() < ( + driver_index + 1 + ): raise ValueError( f"Driver index {driver_index} is out of range. Please use " - + f"the __get_list_of_available_pyodbc_drivers() method " - + f"to list all available drivers." + f"the __get_list_of_available_pyodbc_drivers() method " + f"to list all available drivers." ) - self.driver = self.__get_list_of_supported_pyodbc_drivers()[driver_index] + self.driver = self.__get_list_of_supported_pyodbc_drivers()[ + driver_index + ] @validate_call def __get_number_of_available_pyodbc_drivers(self) -> int: @@ -188,7 +196,9 @@ def __get_list_of_supported_pyodbc_drivers(self) -> List[Any]: return pyodbc.drivers() @validate_call - def __get_list_of_available_and_supported_pyodbc_drivers(self) -> List[Any]: + def __get_list_of_available_and_supported_pyodbc_drivers( + self, + ) -> List[Any]: available_drivers = [] for driver in self.__get_list_of_supported_pyodbc_drivers(): try: @@ -201,7 +211,7 @@ def __get_list_of_available_and_supported_pyodbc_drivers(self) -> List[Any]: timeout=3, ) available_drivers.append(driver) - except pyodbc.Error as e: + except pyodbc.Error: pass return available_drivers @@ -236,8 +246,8 @@ def __connect(self) -> None: except pyodbc.ProgrammingError as err: logger.error(f"Programming Error {err.args[0]}: {err.args[1]}") logger.warning( - f"There seems to be a problem with your code. Please " - + f"check your code and try again." + "There seems to be a problem with your code. Please " + "check your code and try again." ) raise except pyodbc.NotSupportedError as err: @@ -248,11 +258,11 @@ def __connect(self) -> None: except pyodbc.OperationalError as err: logger.error(f"Operational Error {err.args[0]}: {err.args[1]}") logger.warning( - f"Pyodbc is having issues with the connection. This " - + f"could be due to the wrong driver being used. Please " - + f"check your driver with " - + f"the __get_list_of_available_and_supported_pyodbc_drivers() method " - + f"and try again." + "Pyodbc is having issues with the connection. This " + "could be due to the wrong driver being used. Please " + "check your driver with " + "the __get_list_of_available_and_supported_pyodbc_drivers() method " + "and try again." ) attempt += 1 @@ -279,7 +289,7 @@ def __are_connection_attempts_reached(self, attempt) -> bool: logger.error( f"Failed to connect to the DataWarehouse after " - + f"{self.connection_attempts} attempts." + f"{self.connection_attempts} attempts." ) return True diff --git a/src/pyprediktormapclient/dwh/dwh.py b/src/pyprediktormapclient/dwh/dwh.py index a3a95ad..4ca159e 100644 --- a/src/pyprediktormapclient/dwh/dwh.py +++ b/src/pyprediktormapclient/dwh/dwh.py @@ -13,10 +13,10 @@ class DWH(Db, IDWH): - """Helper functions to access a PowerView Data Warehouse or other - SQL databases. This class is a wrapper around pyodbc and you can use - all pyodbc methods as well as the provided methods. Look at the pyodbc - documentation and use the cursor attribute to access the pyodbc cursor. + """Helper functions to access a PowerView Data Warehouse or other SQL + databases. This class is a wrapper around pyodbc and you can use all pyodbc + methods as well as the provided methods. Look at the pyodbc documentation + and use the cursor attribute to access the pyodbc cursor. Args: url (str): The URL of the sql server @@ -65,8 +65,7 @@ def __init__( @validate_call def version(self) -> Dict: - """ - Get the DWH version. + """Get the DWH version. Returns: Dict: A dictionary with the following keys (or similar): DWHVersion, UpdateDate, ImplementedDate, Comment, MajorVersionNo, MinorVersionNo, InterimVersionNo @@ -80,15 +79,17 @@ def version(self) -> Dict: """ def __initialize_context_services(self) -> None: - """ - Initialise all services defined in `context` folder. These are methods - used to directly call certain stored procedures. For instance, class - Enercast contains calls to stored procedures directly related to - Enercast. + """Initialise all services defined in `context` folder. + + These are methods used to directly call certain stored + procedures. For instance, class Enercast contains calls to + stored procedures directly related to Enercast. """ package = context prefix = package.__name__ + "." - for _, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix): + for _, modname, ispkg in pkgutil.iter_modules( + package.__path__, prefix + ): if not ispkg: module = importlib.import_module(modname) diff --git a/src/pyprediktormapclient/model_index.py b/src/pyprediktormapclient/model_index.py index addbeb0..5fdcb43 100644 --- a/src/pyprediktormapclient/model_index.py +++ b/src/pyprediktormapclient/model_index.py @@ -1,9 +1,9 @@ import json import logging from typing import List -import requests -from datetime import date, datetime, timedelta -from pydantic import AnyUrl, validate_call, ValidationError +import requests +from datetime import date, datetime +from pydantic import AnyUrl, ValidationError from pydantic_core import Url from pyprediktormapclient.shared import request_from_api @@ -12,7 +12,7 @@ class ModelIndex: - """Helper functions to access the ModelIndex API server + """Helper functions to access the ModelIndex API server. Args: url (str): The URL of the ModelIndex server with the trailing slash @@ -24,25 +24,36 @@ class ModelIndex: class Config: arbitrary_types_allowed = True - def __init__(self, url: AnyUrl, auth_client: object = None, session: requests.Session = None): + def __init__( + self, + url: AnyUrl, + auth_client: object = None, + session: requests.Session = None, + ): self.url = url - self.headers = {"Content-Type": "application/json", - "Accept": "application/json" - } + self.headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } self.auth_client = auth_client self.session = session if self.auth_client is not None: if self.auth_client.token is not None: - self.headers["Authorization"] = f"Bearer {self.auth_client.token.session_token}" - if hasattr(self.auth_client, 'session_token'): - self.headers["Cookie"] = f"ory_kratos_session={self.auth_client.session_token}" + self.headers["Authorization"] = ( + f"Bearer {self.auth_client.token.session_token}" + ) + if hasattr(self.auth_client, "session_token"): + self.headers["Cookie"] = ( + f"ory_kratos_session={self.auth_client.session_token}" + ) self.body = {"Connection": {"Url": self.url, "AuthenticationType": 1}} self.object_types = self.get_object_types() def json_serial(self, obj): - """JSON serializer for objects not serializable by default json code""" + """JSON serializer for objects not serializable by default json + code.""" if isinstance(obj, (datetime, date)): return obj.isoformat() elif isinstance(obj, Url): @@ -50,30 +61,43 @@ def json_serial(self, obj): raise TypeError(f"Type {type(obj)} not serializable") def check_auth_client(self, content): - if content.get('error', {}).get('code') == 404: + if content.get("error", {}).get("code") == 404: self.auth_client.request_new_ory_token() - self.headers["Authorization"] = f"Bearer {self.auth_client.token.session_token}" + self.headers["Authorization"] = ( + f"Bearer {self.auth_client.token.session_token}" + ) else: raise RuntimeError(content.get("ErrorMessage")) def get_namespace_array(self) -> str: - """Get the namespace array + """Get the namespace array. Returns: str: the JSON returned from the server """ - content = request_from_api(self.url, "GET", "query/namespace-array", headers=self.headers, session=self.session) + content = request_from_api( + self.url, + "GET", + "query/namespace-array", + headers=self.headers, + session=self.session, + ) return content def get_object_types(self) -> str: - content = request_from_api(self.url, "GET", "query/object-types", headers=self.headers, session=self.session) + content = request_from_api( + self.url, + "GET", + "query/object-types", + headers=self.headers, + session=self.session, + ) return content - def get_object_type_id_from_name(self, type_name: str) -> str: - """Get object type id from type name + """Get object type id from type name. Args: type_name (str): type name @@ -83,17 +107,20 @@ def get_object_type_id_from_name(self, type_name: str) -> str: """ try: obj_type = next( - item for item in self.object_types if item["BrowseName"] == type_name + item + for item in self.object_types + if item["BrowseName"] == type_name ) except StopIteration: obj_type = {} - object_type_id = obj_type.get("Id") # Returns None if the ID is not present + object_type_id = obj_type.get( + "Id" + ) # Returns None if the ID is not present return object_type_id - def get_objects_of_type(self, type_name: str) -> str: - """Function to get all the types of an object + """Function to get all the types of an object. Args: type_name (str): type name @@ -106,18 +133,24 @@ def get_objects_of_type(self, type_name: str) -> str: return None body = json.dumps({"typeId": object_type_id}) - content = request_from_api(self.url, "POST", "query/objects-of-type", body, headers=self.headers, session=self.session) + content = request_from_api( + self.url, + "POST", + "query/objects-of-type", + body, + headers=self.headers, + session=self.session, + ) return content - def get_object_descendants( self, type_name: str, ids: List, domain: str, ) -> str: - """A function to get object descendants + """A function to get object descendants. Args: type_name (str): type_name of a descendant @@ -129,7 +162,7 @@ def get_object_descendants( """ if type_name is None or not ids: raise ValidationError("type_name and ids cannot be None or empty") - + id = self.get_object_type_id_from_name(type_name) body = json.dumps( { @@ -138,18 +171,24 @@ def get_object_descendants( "domain": domain, } ) - content = request_from_api(self.url, "POST", "query/object-descendants", body, headers=self.headers, session=self.session) + content = request_from_api( + self.url, + "POST", + "query/object-descendants", + body, + headers=self.headers, + session=self.session, + ) return content - def get_object_ancestors( self, type_name: str, ids: List, domain: str, ) -> str: - """Function to get object ancestors + """Function to get object ancestors. Args: type_name (str): the ancestor parent type @@ -160,8 +199,8 @@ def get_object_ancestors( A json-formatted string with ancestors data of selected object (or None if the type is not found) """ if type_name is None or not ids: - raise ValidationError("type_name and ids cannot be None or empty") - + raise ValidationError("type_name and ids cannot be None or empty") + id = self.get_object_type_id_from_name(type_name) body = json.dumps( { @@ -170,6 +209,13 @@ def get_object_ancestors( "domain": domain, } ) - content = request_from_api(self.url, "POST", "query/object-ancestors", body, headers=self.headers, session=self.session) + content = request_from_api( + self.url, + "POST", + "query/object-ancestors", + body, + headers=self.headers, + session=self.session, + ) return content diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 0dedce8..0d07400 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -31,49 +31,58 @@ class Variables(BaseModel): Namespace: int - Namespace on the signal, e.g. 2. IdType: int - IdTypes described in https://reference.opcfoundation.org/v104/Core/docs/Part3/8.2.3/. """ + Id: str Namespace: int IdType: int + class SubValue(BaseModel): """Helper class to parse all values api's. - Variables: - Type: int - Type of variable, e.g. 12. string, 11. float, etc. list of types described in https://reference.opcfoundation.org/Core/Part6/v104/5.1.2/ - Body: Union[str, float, int, bool] - The value of the varible, should match type. + Variables: + Type: int - Type of variable, e.g. 12. string, 11. float, etc. list of types described in https://reference.opcfoundation.org/Core/Part6/v104/5.1.2/ + Body: Union[str, float, int, bool] - The value of the varible, should match type. """ + Type: int Body: Union[str, float, int, bool] + class HistoryValue(BaseModel): """Helper class to parse all values api's. - Variables: - Value: SubValue - Containing Type and Body (value) of the variable. Described in SubValue class. + Variables: + Value: SubValue - Containing Type and Body (value) of the variable. Described in SubValue class. """ + Value: SubValue + class StatsCode(BaseModel): """Helper class to parse all values api's. - Variables: - Code: Optional[int] - Status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ - Symbol: Optional[str] - String value for status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ + Variables: + Code: Optional[int] - Status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ + Symbol: Optional[str] - String value for status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ """ + Code: Optional[int] = None Symbol: Optional[str] = None + class Value(BaseModel): """Helper class to parse all values api's. - Variables: - Value: SubValue - Containing Type and Body (value) of the variable. Described in SubValue class. - SourceTimestamp: str - Timestamp of the source, e.g. when coming from an API the timestamp returned from the API for the varaible is the sourcetimestamp. - SourcePicoseconds: Optional[int] - Picoseconds for the timestamp of the source if there is a need for a finer granularity, e.g. if samples are sampled in picosecond level or more precision is needed. - ServerTimestamp: Optional[str] - Timestamp for the server, normally this is assigned by the server. - ServerPicoseconds: Optional[int] - Picoseconds for the timestamp on the server, normally this is assigned by the server. - StatusCode: StatusCode - Status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ + Variables: + Value: SubValue - Containing Type and Body (value) of the variable. Described in SubValue class. + SourceTimestamp: str - Timestamp of the source, e.g. when coming from an API the timestamp returned from the API for the varaible is the sourcetimestamp. + SourcePicoseconds: Optional[int] - Picoseconds for the timestamp of the source if there is a need for a finer granularity, e.g. if samples are sampled in picosecond level or more precision is needed. + ServerTimestamp: Optional[str] - Timestamp for the server, normally this is assigned by the server. + ServerPicoseconds: Optional[int] - Picoseconds for the timestamp on the server, normally this is assigned by the server. + StatusCode: StatusCode - Status code, described in https://reference.opcfoundation.org/v104/Core/docs/Part8/A.4.3/ """ + Value: SubValue SourceTimestamp: datetime SourcePicoseconds: Optional[int] = None @@ -81,45 +90,54 @@ class Value(BaseModel): ServerPicoseconds: Optional[int] = None StatusCode: Optional[StatsCode] = None + class WriteVariables(BaseModel): """Helper class for write values api. - Variables: - NodeId: Variables - The complete node'id for the variable - Value: Value - The value to update for the node'id. + Variables: + NodeId: Variables - The complete node'id for the variable + Value: Value - The value to update for the node'id. """ + NodeId: Variables Value: Value + class WriteHistoricalVariables(BaseModel): """Helper class for write historical values api. - Variables: - NodeId (str): The complete node'id for the variable - PerformInsertReplace (int): Historical insertion method 1. Insert, 2. Replace 3. Update, 4. Remove - UpdateValues (list): List of values to update for the node'id. Time must be in descending order. + Variables: + NodeId (str): The complete node'id for the variable + PerformInsertReplace (int): Historical insertion method 1. Insert, 2. Replace 3. Update, 4. Remove + UpdateValues (list): List of values to update for the node'id. Time must be in descending order. """ + NodeId: Variables PerformInsertReplace: int UpdateValues: List[Value] + class WriteVariablesResponse(BaseModel): """Helper class for write historical values api. - Variables: - SymbolCodes: List[StatusCode] - A list of class StatusCode, described in StatusCode class. + Variables: + SymbolCodes: List[StatusCode] - A list of class StatusCode, described in StatusCode class. """ + SymbolCodes: List[StatsCode] -class WriteReturn(BaseModel): - """Helper class to collect API output with API input to see successfull writes for nodes. - Variables: - Id: str - The Id of the signal - Value: str - The written value of the signal - TimeStamp: str - THe SourceTimestamp of the written signal - Success: bool - Success flag for the write operation +class WriteReturn(BaseModel): + """Helper class to collect API output with API input to see successfull + writes for nodes. + + Variables: + Id: str - The Id of the signal + Value: str - The written value of the signal + TimeStamp: str - THe SourceTimestamp of the written signal + Success: bool - Success flag for the write operation """ + Id: str Value: str TimeStamp: str @@ -132,11 +150,13 @@ def run_coroutine(coroutine): loop = asyncio.get_event_loop() return loop.run_until_complete(coroutine) + class Config: - arbitrary_types_allowed = True + arbitrary_types_allowed = True + class OPC_UA: - """Helper functions to access the OPC UA REST Values API server + """Helper functions to access the OPC UA REST Values API server. Args: rest_url (str): The complete url of the OPC UA Values REST API. E.g. "http://127.0.0.1:13371/" @@ -151,12 +171,19 @@ class OPC_UA: * Better session handling with aiohttp * Make sure that time convertions are with timezone """ + class Config: arbitrary_types_allowed = True - - def __init__(self, rest_url: AnyUrl, opcua_url: AnyUrl, namespaces: List = None, auth_client: object = None, session: requests.Session = None): - """Class initializer + def __init__( + self, + rest_url: AnyUrl, + opcua_url: AnyUrl, + namespaces: List = None, + auth_client: object = None, + session: requests.Session = None, + ): + """Class initializer. Args: rest_url (str): The complete url of the OPC UA Values REST API. E.g. "http://127.0.0.1:13371/" @@ -170,36 +197,42 @@ def __init__(self, rest_url: AnyUrl, opcua_url: AnyUrl, namespaces: List = None, self.opcua_url = opcua_url self.headers = { "Content-Type": "application/json", - "Accept": "text/plain" + "Accept": "text/plain", } self.auth_client = auth_client self.session = session self.helper = AsyncIONotebookHelper() - + if self.auth_client is not None: if self.auth_client.token is not None: - self.headers["Authorization"] = f"Bearer {self.auth_client.token.session_token}" - self.body = {"Connection": {"Url": self.opcua_url, "AuthenticationType": 1}} + self.headers["Authorization"] = ( + f"Bearer {self.auth_client.token.session_token}" + ) + self.body = { + "Connection": {"Url": self.opcua_url, "AuthenticationType": 1} + } if namespaces: self.body["ClientNamespaces"] = namespaces def json_serial(self, obj): - """JSON serializer for objects not serializable by default json code""" + """JSON serializer for objects not serializable by default json + code.""" if isinstance(obj, (datetime, date)): return obj.isoformat() elif isinstance(obj, Url): return str(obj) - raise TypeError (f"Type {type(obj)} not serializable") + raise TypeError(f"Type {type(obj)} not serializable") def check_auth_client(self, content): - if (content.get('error').get('code') == 404): + if content.get("error").get("code") == 404: self.auth_client.request_new_ory_token() - self.headers["Authorization"] = f"Bearer {self.auth_client.token.session_token}" + self.headers["Authorization"] = ( + f"Bearer {self.auth_client.token.session_token}" + ) else: raise RuntimeError(content.get("ErrorMessage")) - def _get_value_type(self, id: int) -> Dict: """Internal function to get the type of a value from the OPC UA return,as documentet at https://docs.prediktor.com/docs/opcuavaluesrestapi/datatypes.html#variant @@ -216,7 +249,7 @@ def _get_value_type(self, id: int) -> Dict: def _get_variable_list_as_list(self, variable_list: list) -> list: """Internal function to convert a list of pydantic Variable models to a - list of dicts + list of dicts. Args: variable_list (List): List of pydantic models @@ -226,7 +259,7 @@ def _get_variable_list_as_list(self, variable_list: list) -> list: """ new_vars = [] for var in variable_list: - if hasattr(var, 'model_dump'): + if hasattr(var, "model_dump"): # Convert pydantic model to dict new_vars.append(var.model_dump()) elif isinstance(var, dict): @@ -237,9 +270,8 @@ def _get_variable_list_as_list(self, variable_list: list) -> list: return new_vars - def get_values(self, variable_list: List[Variables]) -> List: - """Request realtime values from the OPC UA server + """Request realtime values from the OPC UA server. Args: variable_list (list): A list of variables you want, containing keys "Id", "Namespace" and "IdType" @@ -264,7 +296,7 @@ def get_values(self, variable_list: List[Variables]) -> List: if self.auth_client is not None: self.check_auth_client(json.loads(e.response.content)) else: - raise RuntimeError(f'Error message {e}') + raise RuntimeError(f"Error message {e}") finally: content = request_from_api( rest_url=self.rest_url, @@ -309,11 +341,12 @@ def get_values(self, variable_list: List[Variables]) -> List: # StatusCode is not always present in the answer if "StatusCode" in contline: vars[num]["StatusCode"] = contline["StatusCode"].get("Code") - vars[num]["StatusSymbol"] = contline["StatusCode"].get("Symbol") + vars[num]["StatusSymbol"] = contline["StatusCode"].get( + "Symbol" + ) return vars - def _check_content(self, content: Dict[str, Any]) -> None: """Check the content returned from the server. @@ -330,23 +363,22 @@ def _check_content(self, content: Dict[str, Any]) -> None: if "HistoryReadResults" not in content: raise RuntimeError(content.get("ErrorMessage")) - def _process_df(self, df_result: pd.DataFrame, columns: Dict[str, str]) -> pd.DataFrame: - """ - Process the DataFrame returned from the server. - """ + def _process_df( + self, df_result: pd.DataFrame, columns: Dict[str, str] + ) -> pd.DataFrame: + """Process the DataFrame returned from the server.""" if "Value.Type" in df_result.columns: - df_result["Value.Type"] = df_result["Value.Type"].replace(self.TYPE_DICT) + df_result["Value.Type"] = df_result["Value.Type"].replace( + self.TYPE_DICT + ) - df_result.rename( - columns=columns, - errors="raise", - inplace=True - ) + df_result.rename(columns=columns, errors="raise", inplace=True) return df_result - - async def _make_request(self, endpoint: str, body: dict, max_retries: int, retry_delay: int): + async def _make_request( + self, endpoint: str, body: dict, max_retries: int, retry_delay: int + ): for attempt in range(max_retries): try: logging.info(f"Attempt {attempt + 1} of {max_retries}") @@ -355,15 +387,21 @@ async def _make_request(self, endpoint: str, body: dict, max_retries: int, retry logging.info(f"Making POST request to {url}") logging.debug(f"Request body: {body}") logging.debug(f"Request headers: {self.headers}") - - async with session.post(url, json=body, headers=self.headers) as response: - logging.info(f"Response received: Status {response.status}") - + + async with session.post( + url, json=body, headers=self.headers + ) as response: + logging.info( + f"Response received: Status {response.status}" + ) + if response.status >= 400: error_text = await response.text() - logging.error(f"HTTP error {response.status}: {error_text}") - response.raise_for_status() - + logging.error( + f"HTTP error {response.status}: {error_text}" + ) + response.raise_for_status() + return await response.json() except aiohttp.ClientResponseError as e: @@ -377,13 +415,15 @@ async def _make_request(self, endpoint: str, body: dict, max_retries: int, retry logging.error(f"Unexpected error in _make_request: {e}") if attempt < max_retries - 1: - wait_time = retry_delay * (2 ** attempt) - logging.warning(f"Request failed. Retrying in {wait_time} seconds...") + wait_time = retry_delay * (2**attempt) + logging.warning( + f"Request failed. Retrying in {wait_time} seconds..." + ) await asyncio.sleep(wait_time) else: - logging.error(f"Max retries reached.") - raise RuntimeError('Max retries reached') - + logging.error("Max retries reached.") + raise RuntimeError("Max retries reached") + def _process_content(self, content: dict) -> pd.DataFrame: self._check_content(content) df_list = [] @@ -392,12 +432,12 @@ def _process_content(self, content: dict) -> pd.DataFrame: for key, value in item["NodeId"].items(): df[f"HistoryReadResults.NodeId.{key}"] = value df_list.append(df) - + if df_list: df_result = pd.concat(df_list) df_result.reset_index(inplace=True, drop=True) return df_result - + async def get_historical_values( self, start_time: datetime, @@ -409,26 +449,36 @@ async def get_historical_values( max_data_points: int = 10000, max_retries: int = 3, retry_delay: int = 5, - max_concurrent_requests: int = 30 + max_concurrent_requests: int = 30, ) -> pd.DataFrame: - """Generic method to request historical values from the OPC UA server with batching""" + """Generic method to request historical values from the OPC UA server + with batching.""" total_time_range_ms = (end_time - start_time).total_seconds() * 1000 estimated_intervals = total_time_range_ms / max_data_points - max_variables_per_batch = max(1, int(max_data_points / estimated_intervals)) + max_variables_per_batch = max( + 1, int(max_data_points / estimated_intervals) + ) max_time_batches = max(1, int(estimated_intervals / max_data_points)) time_batch_size_ms = total_time_range_ms / max_time_batches extended_variables = prepare_variables(variable_list) - variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)] + variable_batches = [ + extended_variables[i : i + max_variables_per_batch] + for i in range(0, len(extended_variables), max_variables_per_batch) + ] semaphore = Semaphore(max_concurrent_requests) async def process_batch(variables, time_batch): async with semaphore: batch_start_ms = time_batch * time_batch_size_ms - batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms) - batch_start = start_time + timedelta(milliseconds=batch_start_ms) + batch_end_ms = min( + (time_batch + 1) * time_batch_size_ms, total_time_range_ms + ) + batch_start = start_time + timedelta( + milliseconds=batch_start_ms + ) batch_end = start_time + timedelta(milliseconds=batch_end_ms) body = { @@ -436,10 +486,12 @@ async def process_batch(variables, time_batch): "StartTime": batch_start.isoformat() + "Z", "EndTime": batch_end.isoformat() + "Z", "ReadValueIds": variables, - **(additional_params or {}) + **(additional_params or {}), } - content = await self._make_request(endpoint, body, max_retries, retry_delay) + content = await self._make_request( + endpoint, body, max_retries, retry_delay + ) return self._process_content(content) tasks = [ @@ -452,7 +504,6 @@ async def process_batch(variables, time_batch): combined_df = pd.concat(results, ignore_index=True) return combined_df - async def get_raw_historical_values_asyn( self, start_time: datetime, @@ -460,13 +511,16 @@ async def get_raw_historical_values_asyn( variable_list: List[str], limit_start_index: Union[int, None] = None, limit_num_records: Union[int, None] = None, - **kwargs + **kwargs, ) -> pd.DataFrame: - """Request raw historical values from the OPC UA server""" + """Request raw historical values from the OPC UA server.""" additional_params = {} if limit_start_index is not None and limit_num_records is not None: - additional_params["Limit"] = {"StartIndex": limit_start_index, "NumRecords": limit_num_records} + additional_params["Limit"] = { + "StartIndex": limit_start_index, + "NumRecords": limit_num_records, + } combined_df = await self.get_historical_values( start_time, @@ -475,7 +529,7 @@ async def get_raw_historical_values_asyn( "values/historical", lambda vars: [{"NodeId": var} for var in vars], additional_params, - **kwargs + **kwargs, ) columns = { "Value.Type": "ValueType", @@ -483,14 +537,15 @@ async def get_raw_historical_values_asyn( "SourceTimestamp": "Timestamp", "HistoryReadResults.NodeId.IdType": "IdType", "HistoryReadResults.NodeId.Id": "Id", - "HistoryReadResults.NodeId.Namespace": "Namespace" + "HistoryReadResults.NodeId.Namespace": "Namespace", } return self._process_df(combined_df, columns) - + def get_raw_historical_values(self, *args, **kwargs): - result = self.helper.run_coroutine(self.get_raw_historical_values_asyn(*args, **kwargs)) - return result - + result = self.helper.run_coroutine( + self.get_raw_historical_values_asyn(*args, **kwargs) + ) + return result async def get_historical_aggregated_values_asyn( self, @@ -499,13 +554,13 @@ async def get_historical_aggregated_values_asyn( pro_interval: int, agg_name: str, variable_list: List[str], - **kwargs + **kwargs, ) -> pd.DataFrame: - """Request historical aggregated values from the OPC UA server""" + """Request historical aggregated values from the OPC UA server.""" additional_params = { "ProcessingInterval": pro_interval, - "AggregateName": agg_name + "AggregateName": agg_name, } combined_df = await self.get_historical_values( @@ -513,9 +568,11 @@ async def get_historical_aggregated_values_asyn( end_time, variable_list, "values/historicalaggregated", - lambda vars: [{"NodeId": var, "AggregateName": agg_name} for var in vars], + lambda vars: [ + {"NodeId": var, "AggregateName": agg_name} for var in vars + ], additional_params, - **kwargs + **kwargs, ) columns = { "Value.Type": "ValueType", @@ -525,17 +582,18 @@ async def get_historical_aggregated_values_asyn( "SourceTimestamp": "Timestamp", "HistoryReadResults.NodeId.IdType": "IdType", "HistoryReadResults.NodeId.Id": "Id", - "HistoryReadResults.NodeId.Namespace": "Namespace" + "HistoryReadResults.NodeId.Namespace": "Namespace", } return self._process_df(combined_df, columns) - + def get_historical_aggregated_values(self, *args, **kwargs): - result = self.helper.run_coroutine(self.get_historical_aggregated_values_asyn(*args, **kwargs)) + result = self.helper.run_coroutine( + self.get_historical_aggregated_values_asyn(*args, **kwargs) + ) return result - - + def write_values(self, variable_list: List[WriteVariables]) -> List: - """Request to write realtime values to the OPC UA server + """Request to write realtime values to the OPC UA server. Args: variable_list (list): A list of variables you want to write to with the value, timestamp and quality, containing keys "Id", "Namespace", "Values" and "IdType". @@ -559,7 +617,7 @@ def write_values(self, variable_list: List[WriteVariables]) -> List: if self.auth_client is not None: self.check_auth_client(json.loads(e.response.content)) else: - raise RuntimeError(f'Error message {e}') + raise RuntimeError(f"Error message {e}") finally: content = request_from_api( rest_url=self.rest_url, @@ -575,17 +633,22 @@ def write_values(self, variable_list: List[WriteVariables]) -> List: if content.get("Success") is False: raise RuntimeError(content.get("ErrorMessage")) if content.get("StatusCodes") is None: - raise ValueError('No status codes returned, might indicate no values written') + raise ValueError( + "No status codes returned, might indicate no values written" + ) # Use to place successfull write next to each written values as API only returns list. Assumes same index in response as in request. for num, row in enumerate(vars): - vars[num]["WriteSuccess"]=(lambda x : True if(x == 0) else False)(content['StatusCodes'][num].get("Code")) + vars[num]["WriteSuccess"] = ( + lambda x: True if (x == 0) else False + )(content["StatusCodes"][num].get("Code")) return vars - - def write_historical_values(self, variable_list: List[WriteHistoricalVariables]) -> List: - """Request to write realtime values to the OPC UA server + def write_historical_values( + self, variable_list: List[WriteHistoricalVariables] + ) -> List: + """Request to write realtime values to the OPC UA server. Args: variable_list (list): A list of variables you want, containing keys "Id", "Namespace", "Values" and "IdType". Values must be in descending order of the timestamps. @@ -594,10 +657,17 @@ def write_historical_values(self, variable_list: List[WriteHistoricalVariables]) """ # Check if data is in correct order, if wrong fail. for variable in variable_list: - if(len(variable.UpdateValues)>1): + if len(variable.UpdateValues) > 1: for num_variable in range(len(variable.UpdateValues) - 1): - if not((variable.UpdateValues[num_variable].SourceTimestamp) < variable.UpdateValues[num_variable+1].SourceTimestamp): - raise ValueError("Time for variables not in correct order.") + if not ( + (variable.UpdateValues[num_variable].SourceTimestamp) + < variable.UpdateValues[ + num_variable + 1 + ].SourceTimestamp + ): + raise ValueError( + "Time for variables not in correct order." + ) # Create a new variable list to remove pydantic models vars = self._get_variable_list_as_list(variable_list) body = copy.deepcopy(self.body) @@ -615,7 +685,7 @@ def write_historical_values(self, variable_list: List[WriteHistoricalVariables]) if self.auth_client is not None: self.check_auth_client(json.loads(e.response.content)) else: - raise RuntimeError(f'Error message {e}') + raise RuntimeError(f"Error message {e}") finally: content = request_from_api( rest_url=self.rest_url, @@ -633,7 +703,9 @@ def write_historical_values(self, variable_list: List[WriteHistoricalVariables]) raise RuntimeError(content.get("ErrorMessage")) # Crash if history report is missing if content.get("HistoryUpdateResults") is None: - raise ValueError('No status codes returned, might indicate no values written') + raise ValueError( + "No status codes returned, might indicate no values written" + ) # Check if there are per history update error codes returned for num_var, variable_row in enumerate(vars): @@ -642,33 +714,53 @@ def write_historical_values(self, variable_list: List[WriteHistoricalVariables]) vars[num_var]["WriteSuccess"] = True else: vars[num_var]["WriteSuccess"] = False - vars[num_var]["WriteError"] = content["HistoryUpdateResults"][num_var].get("StatusCode") + vars[num_var]["WriteError"] = content["HistoryUpdateResults"][ + num_var + ].get("StatusCode") return vars def check_if_ory_session_token_is_valid_refresh(self): - """Check if the session token is still valid - - """ + """Check if the session token is still valid.""" if self.auth_client.check_if_token_has_expired(): self.auth_client.refresh_token() TYPE_LIST = [ - {"id": 0, "type": "Null", "description": "An invalid or unspecified value"}, + { + "id": 0, + "type": "Null", + "description": "An invalid or unspecified value", + }, { "id": 1, "type": "Boolean", "description": "A boolean logic value (true or false)", }, {"id": 2, "type": "SByte", "description": "An 8 bit signed integer value"}, - {"id": 3, "type": "Byte", "description": "An 8 bit unsigned integer value"}, + { + "id": 3, + "type": "Byte", + "description": "An 8 bit unsigned integer value", + }, {"id": 4, "type": "Int16", "description": "A 16 bit signed integer value"}, - {"id": 5, "type": "UInt16", "description": "A 16 bit unsigned integer value"}, + { + "id": 5, + "type": "UInt16", + "description": "A 16 bit unsigned integer value", + }, {"id": 6, "type": "Int32", "description": "A 32 bit signed integer value"}, - {"id": 7, "type": "UInt32", "description": "A 32 bit unsigned integer value"}, + { + "id": 7, + "type": "UInt32", + "description": "A 32 bit unsigned integer value", + }, {"id": 8, "type": "Int64", "description": "A 64 bit signed integer value"}, - {"id": 9, "type": "UInt64", "description": "A 64 bit unsigned integer value"}, + { + "id": 9, + "type": "UInt64", + "description": "A 64 bit unsigned integer value", + }, { "id": 10, "type": "Float", @@ -679,9 +771,17 @@ def check_if_ory_session_token_is_valid_refresh(self): "type": "Double", "description": "An IEEE double precision (64 bit) floating point value", }, - {"id": 12, "type": "String", "description": "A sequence of Unicode characters"}, + { + "id": 12, + "type": "String", + "description": "A sequence of Unicode characters", + }, {"id": 13, "type": "DateTime", "description": "An instance in time"}, - {"id": 14, "type": "Guid", "description": "A 128-bit globally unique identifier"}, + { + "id": 14, + "type": "Guid", + "description": "A 128-bit globally unique identifier", + }, {"id": 15, "type": "ByteString", "description": "A sequence of bytes"}, {"id": 16, "type": "XmlElement", "description": "An XML element"}, { @@ -694,7 +794,11 @@ def check_if_ory_session_token_is_valid_refresh(self): "type": "ExpandedNodeId", "description": "A node id that stores the namespace URI instead of the namespace index", }, - {"id": 19, "type": "StatusCode", "description": "A structured result code"}, + { + "id": 19, + "type": "StatusCode", + "description": "A structured result code", + }, { "id": 20, "type": "QualifiedName", @@ -715,10 +819,14 @@ def check_if_ory_session_token_is_valid_refresh(self): "type": "DataValue", "description": "A data value with an associated quality and timestamp", }, - {"id": 24, "type": "Variant", "description": "Any of the other built-in types"}, + { + "id": 24, + "type": "Variant", + "description": "Any of the other built-in types", + }, { "id": 25, "type": "DiagnosticInfo", "description": "A diagnostic information associated with a result code", }, -] \ No newline at end of file +] diff --git a/src/pyprediktormapclient/shared.py b/src/pyprediktormapclient/shared.py index 09511f9..a8bce3b 100644 --- a/src/pyprediktormapclient/shared.py +++ b/src/pyprediktormapclient/shared.py @@ -1,11 +1,11 @@ -import asyncio -import aiohttp import requests from pydantic import AnyUrl, ValidationError -from typing import Literal, Dict, Any +from typing import Literal + class Config: - arbitrary_types_allowed = True + arbitrary_types_allowed = True + def request_from_api( rest_url: AnyUrl, @@ -17,7 +17,7 @@ def request_from_api( extended_timeout: bool = False, session: requests.Session = None, ) -> str: - """Function to perform the request to the ModelIndex server + """Function to perform the request to the ModelIndex server. Args: rest_url (str): The URL with trailing shash @@ -35,19 +35,28 @@ def request_from_api( request_method = session if session else requests if method == "GET": - result = request_method.get(combined_url, timeout=request_timeout, params=params, headers=headers) + result = request_method.get( + combined_url, + timeout=request_timeout, + params=params, + headers=headers, + ) if method == "POST": result = request_method.post( - combined_url, data=data, headers=headers, timeout=request_timeout, params=params + combined_url, + data=data, + headers=headers, + timeout=request_timeout, + params=params, ) - + if method not in ["GET", "POST"]: raise ValidationError("Unsupported method") - + result.raise_for_status() - if 'application/json' in result.headers.get('Content-Type', ''): + if "application/json" in result.headers.get("Content-Type", ""): return result.json() else: diff --git a/tests/test_analytics_helper.py b/tests/analytics_helper_test.py similarity index 96% rename from tests/test_analytics_helper.py rename to tests/analytics_helper_test.py index 96e5f6e..0898ae3 100644 --- a/tests/test_analytics_helper.py +++ b/tests/analytics_helper_test.py @@ -1,5 +1,4 @@ import unittest -import json import pandas as pd import pytest from pydantic import ValidationError @@ -70,6 +69,7 @@ {"Idx": 6, "Uri": "http://prediktor.no/PVTypes/"}, ] + # Our test case class class AnalyticsHelperTestCase(unittest.TestCase): def test_analytics_helper_initialization_success(self): @@ -84,9 +84,15 @@ def test_analytics_helper_initialization_success(self): assert "SomeName" in result.list_of_variable_names() assert "SomeName2" in result.list_of_variable_names() assert "Property" in result.properties_as_dataframe() - assert "Property1" in result.properties_as_dataframe()["Property"].to_list() + assert ( + "Property1" + in result.properties_as_dataframe()["Property"].to_list() + ) assert "VariableName" in result.variables_as_dataframe() - assert "SomeName" in result.variables_as_dataframe()["VariableName"].to_list() + assert ( + "SomeName" + in result.variables_as_dataframe()["VariableName"].to_list() + ) def test_split_id_success(self): instance = AnalyticsHelper(proper_json) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py new file mode 100644 index 0000000..c706df6 --- /dev/null +++ b/tests/auth_client_test.py @@ -0,0 +1,464 @@ +import unittest +from unittest import mock +import pytest +from pydantic import ValidationError, BaseModel, AnyUrl +from copy import deepcopy +import datetime + +from pyprediktormapclient.auth_client import AUTH_CLIENT, Token + +URL = "http://someserver.somedomain.com/v1/" +username = "some@user.com" +password = "somepassword" +auth_id = "0b518533-fb09-4bb7-a51f-166d3453685e" +auth_session_id = "qlZULxcaNc6xVdXQfqPxwix5v3tuCLaO" +auth_expires_at = "2022-12-04T07:31:28.767407252Z" +auth_expires_at_2hrs_ago = "2022-12-04T05:31:28.767407252Z" + +successful_self_service_login_token_mocked_response = { + "Success": True, + "ErrorMessage": "", + "ErrorCode": 0, + "session_token": "qlZULxcaNc6xVdXQfqPxwix5v3tuCLaO", + "session": { + "id": "b26e9032-c34b-4f14-a912-c055b12f02da", + "active": True, + "expires_at": "2022-12-04T07:31:28.767407252Z", + "authenticated_at": "2022-12-01T07:31:28.767407252Z", + "authenticator_assurance_level": "aal1", + "authentication_methods": [ + { + "method": "password", + "aal": "aal1", + "completed_at": "2022-12-01T07:31:28.767403652Z", + } + ], + "issued_at": "2022-12-01T07:31:28.767407252Z", + "identity": { + "id": "24f9466f-4e81-42d8-8a40-1f46f2203c19", + "schema_id": "preset://email", + "schema_url": "https://authauth.blabla.io/schemas/cHJlc2V0Oi8vZW1haWw", + "state": "active", + "state_changed_at": "2022-08-15T12:43:28.623721Z", + "traits": {"email": "api@prediktor.com"}, + "verifiable_addresses": [ + { + "id": "c083c747-eee9-4ee0-a7b9-96aa067bcea6", + "value": "api@prediktor.com", + "verified": False, + "via": "email", + "status": "sent", + "created_at": "2022-08-15T12:43:28.631351Z", + "updated_at": "2022-08-15T12:43:28.631351Z", + } + ], + "recovery_addresses": [ + { + "id": "f907259d-52d9-420b-985a-004b6e745bd0", + "value": "api@prediktor.com", + "via": "email", + "created_at": "2022-08-15T12:43:28.637771Z", + "updated_at": "2022-08-15T12:43:28.637771Z", + } + ], + "metadata_public": None, + "created_at": "2022-08-15T12:43:28.626258Z", + "updated_at": "2022-08-15T12:43:28.626258Z", + }, + "devices": [ + { + "id": "5f805841-d4fc-461f-ad1b-87ca4166355d", + "ip_address": "81.166.54.38", + "user_agent": "Go-http-client/1.1", + "location": "Fredrikstad, NO", + } + ], + }, +} + +# TODO handle old id to get new token, use use_flow_id, investigate if this works +expired_self_service_logon_response = { + "error": { + "id": "self_service_flow_expired", + "code": 410, + "status": "Gone", + "reason": "The self-service flow expired 904.65 minutes ago, initialize a new one.", + "message": "self-service flow expired", + }, + "expired_at": "2022-11-24T15:37:59.690422Z", + "since": 54279099192303, + "use_flow_id": "2b132a0d-f3a5-4eff-8a1b-f785228168a9", +} + + +successfull_self_service_login_response = { + "Success": True, + "ErrorMessage": "", + "ErrorCode": 0, + "id": "0b518533-fb09-4bb7-a51f-166d3453685e", + "oauth2_login_challenge": None, + "type": "api", + "expires_at": "2022-11-24T15:37:59.690422428Z", + "issued_at": "2022-11-24T14:37:59.690422428Z", + "request_url": "https://authauth.blabla.io/self-service/login/api", + "ui": { + "action": "https://authauth.blabla.io/self-service/login?flow=0b518533-fb09-4bb7-a51f-166d3453685e", + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "value": "", + "required": True, + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": {}, + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "text", + "value": "", + "required": True, + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": {"id": 1070004, "text": "ID", "type": "info"} + }, + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": True, + "autocomplete": "current-password", + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info", + } + }, + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": { + "id": 1010001, + "text": "Sign in", + "type": "info", + "context": {}, + } + }, + }, + ], + }, + "created_at": "2022-11-24T14:37:59.770365Z", + "updated_at": "2022-11-24T14:37:59.770365Z", + "refresh": False, + "requested_aal": "aal1", +} +empty_self_service_login_response = { + "Success": True, + "ErrorMessage": "", + "ErrorCode": 0, + "oauth2_login_challenge": None, + "type": "api", + "expires_at": "2022-11-24T15:37:59.690422428Z", + "issued_at": "2022-11-24T14:37:59.690422428Z", + "request_url": "https://authauth.blabla.io/self-service/login/api", + "ui": { + "method": "POST", + "nodes": [ + { + "type": "input", + "group": "default", + "attributes": { + "name": "csrf_token", + "type": "hidden", + "value": "", + "required": True, + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": {}, + }, + { + "type": "input", + "group": "default", + "attributes": { + "name": "identifier", + "type": "text", + "value": "", + "required": True, + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": {"id": 1070004, "text": "ID", "type": "info"} + }, + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "password", + "type": "password", + "required": True, + "autocomplete": "current-password", + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": { + "id": 1070001, + "text": "Password", + "type": "info", + } + }, + }, + { + "type": "input", + "group": "password", + "attributes": { + "name": "method", + "type": "submit", + "value": "password", + "disabled": False, + "node_type": "input", + }, + "messages": [], + "meta": { + "label": { + "id": 1010001, + "text": "Sign in", + "type": "info", + "context": {}, + } + }, + }, + ], + }, + "created_at": "2022-11-24T14:37:59.770365Z", + "updated_at": "2022-11-24T14:37:59.770365Z", + "refresh": False, + "requested_aal": "aal1", +} + + +class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + self.raise_for_status = mock.Mock(return_value=False) + self.headers = {"Content-Type": "application/json"} + + def json(self): + return self.json_data + + +# This method will be used by the mock to replace requests +def successful_self_service_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login/api": + return MockResponse(successfull_self_service_login_response, 200) + return MockResponse(None, 404) + + +def empty_self_service_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login/api": + return MockResponse(empty_self_service_login_response, 200) + return MockResponse(None, 404) + + +def unsuccessful_self_service_login_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login/api": + # Set Success to False + unsuc = deepcopy(successfull_self_service_login_response) + unsuc["Success"] = False + return MockResponse(unsuc, 200) + + return MockResponse(None, 404) + + +def wrong_id_self_service_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login/api": + unsuc = deepcopy(successfull_self_service_login_response) + unsuc["id"] = 33 + return MockResponse(unsuc, 200) + + return MockResponse(None, 404) + + +def successful_self_service_login_token_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login": + return MockResponse( + successful_self_service_login_token_mocked_response, 200 + ) + + return MockResponse(None, 404) + + +def unsuccessful_self_service_login_token_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login": + unsuc = deepcopy(successful_self_service_login_token_mocked_response) + unsuc["Success"] = False + return MockResponse(unsuc, 200) + + return MockResponse(None, 404) + + +def empty_self_service_login_token_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login": + return MockResponse(empty_self_service_login_response, 200) + + return MockResponse(None, 404) + + +def expired_self_service_login_mocked_requests(*args, **kwargs): + if args[0] == f"{URL}self-service/login": + return MockResponse(expired_self_service_login_mocked_requests, 200) + + return MockResponse(None, 404) + + +class AnyUrlModel(BaseModel): + url: AnyUrl + + +# Our test case class +class OPCUATestCase(unittest.TestCase): + + def test_malformed_rest_url(self): + with pytest.raises(ValidationError): + AnyUrlModel(rest_url="invalid-url") + + @mock.patch( + "requests.get", side_effect=successful_self_service_mocked_requests + ) + def test_get_self_service_login_id_successful(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.get_login_id() + assert auth_client.id == auth_id + + @mock.patch( + "requests.get", + side_effect=unsuccessful_self_service_login_mocked_requests, + ) + def test_get_self_service_login_id_unsuccessful(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + with pytest.raises(RuntimeError): + auth_client.get_login_id() + + @mock.patch("requests.get", side_effect=empty_self_service_mocked_requests) + def test_get_self_service_login_id_empty(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + with pytest.raises(RuntimeError): + auth_client.get_login_id() + + @mock.patch( + "requests.get", side_effect=wrong_id_self_service_mocked_requests + ) + def test_get_self_service_login_id_wrong_id(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + with pytest.raises(RuntimeError): + auth_client.get_login_id() + + @mock.patch( + "requests.post", + side_effect=empty_self_service_login_token_mocked_requests, + ) + def test_get_self_service_login_token_empty(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.id = auth_id + with pytest.raises(RuntimeError): + auth_client.get_login_token() + + @mock.patch( + "requests.post", + side_effect=successful_self_service_login_token_mocked_requests, + ) + def test_get_self_service_login_token_successful(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.id = auth_id + auth_client.get_login_token() + test_token = Token( + session_token=auth_session_id, expires_at=auth_expires_at + ) + assert auth_client.token.session_token == test_token.session_token + assert auth_client.token.expires_at == test_token.expires_at + + @mock.patch( + "requests.post", + side_effect=unsuccessful_self_service_login_token_mocked_requests, + ) + def test_get_self_service_login_token_unsuccessful(self, mock_get): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.id = auth_id + with pytest.raises(RuntimeError): + auth_client.get_login_token() + + def test_get_self_service_token_expired(self): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.token = Token( + session_token=auth_session_id, expires_at=auth_expires_at_2hrs_ago + ) + auth_client.token.expires_at = datetime.datetime.now( + datetime.timezone.utc + ) - datetime.timedelta(hours=2) + token_expired = auth_client.check_if_token_has_expired() + assert token_expired + + def test_get_self_service_token_expired_none(self): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.token = Token(session_token=auth_session_id) + token_expired = auth_client.check_if_token_has_expired() + assert token_expired + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/conftest.py b/tests/conftest.py index 83e1f57..e592c31 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,9 @@ -""" - Dummy conftest.py for pyprediktormapclient. +"""Dummy conftest.py for pyprediktormapclient. - If you don't know what this is for, just leave it empty. - Read more about conftest.py under: - - https://docs.pytest.org/en/stable/fixture.html - - https://docs.pytest.org/en/stable/writing_plugins.html +If you don't know what this is for, just leave it empty. +Read more about conftest.py under: +- https://docs.pytest.org/en/stable/fixture.html +- https://docs.pytest.org/en/stable/writing_plugins.html """ # import pytest diff --git a/tests/dwh/context/test_enercast.py b/tests/dwh/context/enercast_test.py similarity index 81% rename from tests/dwh/context/test_enercast.py rename to tests/dwh/context/enercast_test.py index f97fffe..c20b9ad 100644 --- a/tests/dwh/context/test_enercast.py +++ b/tests/dwh/context/enercast_test.py @@ -92,16 +92,30 @@ def test_upsert_forecast_data(monkeypatch): "facilityName": "SomePlant", "powerUnit": "W", "values": [ - {"timestamp": "2023-11-20T17:30:00Z", "normed": 0, "absolute": 0}, - {"timestamp": "2023-11-20T17:45:00Z", "normed": 0, "absolute": 0}, - {"timestamp": "2023-11-20T18:00:00Z", "normed": 0, "absolute": 0}, + { + "timestamp": "2023-11-20T17:30:00Z", + "normed": 0, + "absolute": 0, + }, + { + "timestamp": "2023-11-20T17:45:00Z", + "normed": 0, + "absolute": 0, + }, + { + "timestamp": "2023-11-20T18:00:00Z", + "normed": 0, + "absolute": 0, + }, ], } } forecast_type_key = 1 - enercast_forecast_data_json = json.dumps({"results": enercast_forecast_data}) - expected_query = f"EXEC dwetl.UpsertEnercastForecastData ?, ?" + enercast_forecast_data_json = json.dumps( + {"results": enercast_forecast_data} + ) + expected_query = "EXEC dwetl.UpsertEnercastForecastData ?, ?" expected_result = [] mock_dwh = Mock(spec=IDWH) diff --git a/tests/dwh/context/test_plant.py b/tests/dwh/context/plant_test.py similarity index 72% rename from tests/dwh/context/test_plant.py rename to tests/dwh/context/plant_test.py index 0009fe2..a00c46c 100644 --- a/tests/dwh/context/test_plant.py +++ b/tests/dwh/context/plant_test.py @@ -24,8 +24,8 @@ def test_get_optimal_tracker_angles(monkeypatch): facility_name = "AnotherPlant" expected_query = ( - f"SET NOCOUNT ON; EXEC dwetl.GetOptimalTrackerAngleParameters " - + f"@FacilityName = N'{facility_name}'" + "SET NOCOUNT ON; EXEC dwetl.GetOptimalTrackerAngleParameters " + f"@FacilityName = N'{facility_name}'" ) expected_result = [] @@ -49,7 +49,7 @@ def test_upsert_optimal_tracker_angles(monkeypatch): facility_data_json = json.dumps(facility_data) facility_data_json.replace("'", '"') - expected_query = f"EXEC dwetl.UpsertOptimalTrackerAngles @json = ?" + expected_query = "EXEC dwetl.UpsertOptimalTrackerAngles @json = ?" expected_result = [] mock_dwh = Mock(spec=IDWH) @@ -58,7 +58,9 @@ def test_upsert_optimal_tracker_angles(monkeypatch): plant = Plant(mock_dwh) actual_result = plant.upsert_optimal_tracker_angles(facility_data) - mock_dwh.execute.assert_called_once_with(expected_query, facility_data_json) + mock_dwh.execute.assert_called_once_with( + expected_query, facility_data_json + ) assert actual_result == expected_result @@ -67,7 +69,9 @@ def test_upsert_optimal_tracker_angles(monkeypatch): """ -def test_insert_log_when_has_thrown_error_is_false_then_result_is_ok(monkeypatch): +def test_insert_log_when_has_thrown_error_is_false_then_result_is_ok( + monkeypatch, +): message = "Some message" plantname = "XY-ZK2" data_type = "Forecast" @@ -75,12 +79,12 @@ def test_insert_log_when_has_thrown_error_is_false_then_result_is_ok(monkeypatch ext_forecast_type_key = 11 expected_query = ( - f"EXEC dwetl.InsertExtDataUpdateLog " - + f"@plantname = ?, " - + f"@extkey = ?, " - + f"@DataType = ?, " - + f"@Message = ?, " - + f"@Result = ?" + "EXEC dwetl.InsertExtDataUpdateLog " + "@plantname = ?, " + "@extkey = ?, " + "@DataType = ?, " + "@Message = ?, " + "@Result = ?" ) expected_result = [] @@ -93,12 +97,19 @@ def test_insert_log_when_has_thrown_error_is_false_then_result_is_ok(monkeypatch ) mock_dwh.execute.assert_called_once_with( - expected_query, plantname, ext_forecast_type_key, data_type, message, "OK" + expected_query, + plantname, + ext_forecast_type_key, + data_type, + message, + "OK", ) assert actual_result == expected_result -def test_insert_log_when_has_thrown_error_is_true_then_result_is_error(monkeypatch): +def test_insert_log_when_has_thrown_error_is_true_then_result_is_error( + monkeypatch, +): message = "Some message" plantname = "XY-ZK2" data_type = "Forecast" @@ -106,12 +117,12 @@ def test_insert_log_when_has_thrown_error_is_true_then_result_is_error(monkeypat ext_forecast_type_key = 11 expected_query = ( - f"EXEC dwetl.InsertExtDataUpdateLog " - + f"@plantname = ?, " - + f"@extkey = ?, " - + f"@DataType = ?, " - + f"@Message = ?, " - + f"@Result = ?" + "EXEC dwetl.InsertExtDataUpdateLog " + "@plantname = ?, " + "@extkey = ?, " + "@DataType = ?, " + "@Message = ?, " + "@Result = ?" ) expected_result = [] @@ -124,6 +135,11 @@ def test_insert_log_when_has_thrown_error_is_true_then_result_is_error(monkeypat ) mock_dwh.execute.assert_called_once_with( - expected_query, plantname, ext_forecast_type_key, data_type, message, "ERROR" + expected_query, + plantname, + ext_forecast_type_key, + data_type, + message, + "ERROR", ) assert actual_result == expected_result diff --git a/tests/dwh/context/test_solcast.py b/tests/dwh/context/solcast_test.py similarity index 98% rename from tests/dwh/context/test_solcast.py rename to tests/dwh/context/solcast_test.py index 613aab6..92330b5 100644 --- a/tests/dwh/context/test_solcast.py +++ b/tests/dwh/context/solcast_test.py @@ -80,7 +80,7 @@ def test_upsert_forecast_data(monkeypatch): } } ) - expected_query = f"EXEC dwetl.UpsertSolcastForecastData ?, ?" + expected_query = "EXEC dwetl.UpsertSolcastForecastData ?, ?" expected_result = { "data": [ { diff --git a/tests/dwh/test_db.py b/tests/dwh/db_test.py similarity index 92% rename from tests/dwh/test_db.py rename to tests/dwh/db_test.py index 8bd596c..657fd7d 100644 --- a/tests/dwh/test_db.py +++ b/tests/dwh/db_test.py @@ -11,7 +11,8 @@ """ Helpers """ - + + class mock_pyodbc_connection: def __init__(self, connection_string): pass @@ -20,17 +21,23 @@ def cursor(self): return -def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts(connection_string): +def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts( + connection_string, +): raise pyodbc.DataError("Error code", "Error message") -def mock_pyodbc_connection_throws_error_tolerant_to_attempts(connection_string): +def mock_pyodbc_connection_throws_error_tolerant_to_attempts( + connection_string, +): raise pyodbc.DatabaseError("Error code", "Error message") def grs(): """Generate a random string.""" - return "".join(random.choices(string.ascii_uppercase + string.digits, k=10)) + return "".join( + random.choices(string.ascii_uppercase + string.digits, k=10) + ) """ @@ -56,7 +63,9 @@ def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exc driver_index = 0 # Mock the absence of ODBC drivers - monkeypatch.setattr("pyprediktormapclient.dwh.db.pyodbc.drivers", lambda: []) + monkeypatch.setattr( + "pyprediktormapclient.dwh.db.pyodbc.drivers", lambda: [] + ) with pytest.raises(ValueError) as excinfo: Db(grs(), grs(), grs(), grs(), driver_index) @@ -106,7 +115,9 @@ def test_init_when_instantiate_dwh_but_driver_index_is_not_passed_then_instance_ mock_cursor = Mock() mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2"]) db = Db(grs(), grs(), grs(), grs()) @@ -173,8 +184,12 @@ def test_fetch_when_to_dataframe_is_false_and_no_data_is_returned_then_return_em # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query) @@ -265,8 +280,12 @@ def test_fetch_when_to_dataframe_is_false_and_single_data_set_is_returned_then_r # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query) @@ -402,8 +421,12 @@ def test_fetch_when_to_dataframe_is_false_and_multiple_data_sets_are_returned_th # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query) @@ -435,8 +458,12 @@ def test_fetch_when_to_dataframe_is_true_and_no_data_is_returned_then_return_emp # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query, True) @@ -528,8 +555,12 @@ def test_fetch_when_to_dataframe_is_true_and_single_data_set_is_returned_then_re # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query, True) @@ -669,8 +700,12 @@ def test_fetch_when_to_dataframe_is_true_and_multiple_data_sets_are_returned_the # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) db = Db(grs(), grs(), grs(), grs(), driver_index) actual_result = db.fetch(query, True) @@ -722,7 +757,9 @@ def test_execute_when_init_db_connection_is_successfull_but_fails_when_calling_e db.execute(query) -def test_execute_when_parameter_passed_then_fetch_results_and_return_data(monkeypatch): +def test_execute_when_parameter_passed_then_fetch_results_and_return_data( + monkeypatch, +): query = "INSERT INTO mytable VALUES (?, ?)" param_one = "John" param_two = "Smith" @@ -755,7 +792,9 @@ def test_execute_when_parameter_passed_then_fetch_results_and_return_data(monkey assert actual_result == expected_result -def test_execute_when_fetchall_throws_error_then_return_empty_list(monkeypatch): +def test_execute_when_fetchall_throws_error_then_return_empty_list( + monkeypatch, +): query = "INSERT INTO mytable VALUES (?, ?)" param_one = "John" param_two = "Smith" diff --git a/tests/dwh/test_dwh.py b/tests/dwh/dwh_test.py similarity index 87% rename from tests/dwh/test_dwh.py rename to tests/dwh/dwh_test.py index 7860780..da186a6 100644 --- a/tests/dwh/test_dwh.py +++ b/tests/dwh/dwh_test.py @@ -1,6 +1,4 @@ import pytest -import random -import string import pyodbc import logging import datetime @@ -11,27 +9,38 @@ Mock Functions """ -def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts(connection_string): + +def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts( + connection_string, +): raise pyodbc.DataError("Error code", "Error message") -def mock_pyodbc_connection_throws_error_tolerant_to_attempts(connection_string): + +def mock_pyodbc_connection_throws_error_tolerant_to_attempts( + connection_string, +): def attempt_connect(): if attempt_connect.counter < 3: attempt_connect.counter += 1 raise pyodbc.DatabaseError("Error code", "Temporary error message") else: raise pyodbc.DatabaseError("Error code", "Permanent error message") + attempt_connect.counter = 0 return attempt_connect() + """ Helper Function """ + def grs(): - """Generate a random string suitable for URL, database, username, password.""" + """Generate a random string suitable for URL, database, username, + password.""" return "test_string" + """ Test Functions """ @@ -80,7 +89,9 @@ def test_init_when_instantiate_dwh_but_driver_index_is_not_passed_then_instance_ mock_cursor = Mock() mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2"]) dwh = DWH(grs(), grs(), grs(), grs()) @@ -93,7 +104,9 @@ def test_init_when_instantiate_dwh_but_driver_index_is_not_passed_then_instance_ """ -def test_version_when_version_data_is_returned_then_return_version_data(monkeypatch): +def test_version_when_version_data_is_returned_then_return_version_data( + monkeypatch, +): driver_index = 2 data_returned_by_dwh = [ ( @@ -132,8 +145,12 @@ def test_version_when_version_data_is_returned_then_return_version_data(monkeypa # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) dwh = DWH(grs(), grs(), grs(), grs(), driver_index) version = dwh.version() @@ -142,7 +159,9 @@ def test_version_when_version_data_is_returned_then_return_version_data(monkeypa assert version == expected_result -def test_version_when_version_data_is_not_returned_then_return_empty_tuple(monkeypatch): +def test_version_when_version_data_is_not_returned_then_return_empty_tuple( + monkeypatch, +): driver_index = 2 expected_query = "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" expected_result = {} @@ -163,8 +182,12 @@ def test_version_when_version_data_is_not_returned_then_return_empty_tuple(monke # Mock the connection method to return a mock connection with a mock cursor mock_connection = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr("pyodbc.connect", lambda *args, **kwargs: mock_connection) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"]) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) dwh = DWH(grs(), grs(), grs(), grs(), driver_index) version = dwh.version() diff --git a/tests/test_model_index.py b/tests/model_index_test.py similarity index 94% rename from tests/test_model_index.py rename to tests/model_index_test.py index a9b8a5b..d940f6e 100644 --- a/tests/test_model_index.py +++ b/tests/model_index_test.py @@ -1,4 +1,3 @@ -import requests import pytest import unittest from unittest import mock @@ -58,6 +57,7 @@ } ] + # This method will be used by the mock to replace requests.get def mocked_requests(*args, **kwargs): class MockResponse: @@ -65,7 +65,7 @@ def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code self.raise_for_status = mock.Mock(return_value=False) - self.headers = {'Content-Type': 'application/json'} + self.headers = {"Content-Type": "application/json"} def json(self): return self.json_data @@ -87,6 +87,7 @@ def json(self): class AnyUrlModel(BaseModel): url: AnyUrl + # Our test case class class ModelIndexTestCase(unittest.TestCase): def test_malformed_url(self): @@ -117,14 +118,16 @@ def test_get_object_of_type_with_wrong_type(self, mock_get): model = ModelIndex(url=URL) with mock.patch("requests.post", side_effect=mocked_requests): result = model.get_objects_of_type(type_name="IPVBaseCalculate2") - assert result == None + assert result is None @mock.patch("requests.get", side_effect=mocked_requests) def test_get_object_descendants(self, mock_get): model = ModelIndex(url=URL) with mock.patch("requests.post", side_effect=mocked_requests): result = model.get_object_descendants( - type_name="IPVBaseCalculate", ids=["Anything"], domain="PV_Assets" + type_name="IPVBaseCalculate", + ids=["Anything"], + domain="PV_Assets", ) assert result == descendants @@ -135,7 +138,7 @@ def test_get_object_descendants_with_no_type_id(self, mock_get): result = model.get_object_descendants( type_name=None, ids=["Anything"], domain="PV_Assets" ) - assert result == None + assert result is None @mock.patch("requests.get", side_effect=mocked_requests) def test_get_object_descendants_with_no_ids(self, mock_get): @@ -150,7 +153,9 @@ def test_get_object_ancestors(self, mock_get): model = ModelIndex(url=URL) with mock.patch("requests.post", side_effect=mocked_requests): result = model.get_object_ancestors( - type_name="IPVBaseCalculate", ids=["Anything"], domain="PV_Assets" + type_name="IPVBaseCalculate", + ids=["Anything"], + domain="PV_Assets", ) assert result == ancestors diff --git a/tests/test_opc_ua.py b/tests/opc_ua_test.py similarity index 70% rename from tests/test_opc_ua.py rename to tests/opc_ua_test.py index cf8a991..3ec78c6 100644 --- a/tests/test_opc_ua.py +++ b/tests/opc_ua_test.py @@ -8,7 +8,7 @@ from typing import List from copy import deepcopy -from pyprediktormapclient.opc_ua import OPC_UA, Variables +from pyprediktormapclient.opc_ua import OPC_UA from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -25,149 +25,91 @@ list_of_write_values = [ { - "NodeId": { - 'Id': 'SOMEID', - 'Namespace': 1, - 'IdType': 2 - }, + "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, "Value": { - "Value": { - "Type": 10, - "Body": 1.2 - }, + "Value": {"Type": 10, "Body": 1.2}, "SourceTimestamp": "2022-11-03T12:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - } + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, } ] list_of_write_historical_values = [ - { - "NodeId": { - "Id": "SOMEID", - "Namespace": 1, - "IdType": 2 + { + "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, + "PerformInsertReplace": 1, + "UpdateValues": [ + { + "Value": {"Type": 10, "Body": 1.1}, + "SourceTimestamp": "2022-11-03T12:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, }, - "PerformInsertReplace": 1, - "UpdateValues": [ - { - "Value": { - "Type": 10, - "Body": 1.1 - }, - "SourceTimestamp": "2022-11-03T12:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - }, - { - "Value": { - "Type": 10, - "Body": 2.1 - }, - "SourceTimestamp": "2022-11-03T13:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - } - ] - } - ] + { + "Value": {"Type": 10, "Body": 2.1}, + "SourceTimestamp": "2022-11-03T13:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + ], + } +] list_of_write_historical_values_in_wrong_order = [ + { + "NodeId": {"Id": "SOMEID", "Namespace": 4, "IdType": 1}, + "PerformInsertReplace": 1, + "UpdateValues": [ { - "NodeId": { - "Id": "SOMEID", - "Namespace": 4, - "IdType": 1 - }, - "PerformInsertReplace": 1, - "UpdateValues": [ - { - "Value": { - "Type": 10, - "Body": 1.1 - }, - "SourceTimestamp": "2022-11-03T14:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - }, - { - "Value": { - "Type": 10, - "Body": 2.1 - }, - "SourceTimestamp": "2022-11-03T13:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - } - ] - } - ] + "Value": {"Type": 10, "Body": 1.1}, + "SourceTimestamp": "2022-11-03T14:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + { + "Value": {"Type": 10, "Body": 2.1}, + "SourceTimestamp": "2022-11-03T13:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + ], + } +] list_of_historical_values_wrong_type_and_value = [ + { + "NodeId": {"Id": "SSO.JO-GL.321321321", "Namespace": 4, "IdType": 1}, + "PerformInsertReplace": 1, + "UpdateValues": [ { - "NodeId": { - "Id": "SSO.JO-GL.321321321", - "Namespace": 4, - "IdType": 1 - }, - "PerformInsertReplace": 1, - "UpdateValues": [ - { - "Value": { - "Type": 10, - "Body": 1.1 - }, - "SourceTimestamp": "2022-11-03T14:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - }, - { - "Value": { - "Type": 1, - "Body": "2.1" - }, - "SourceTimestamp": "2022-11-03T15:00:00Z", - "StatusCode": { - "Code": 0, - "Symbol": "Good" - } - } - ] - } - ] + "Value": {"Type": 10, "Body": 1.1}, + "SourceTimestamp": "2022-11-03T14:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + { + "Value": {"Type": 1, "Body": "2.1"}, + "SourceTimestamp": "2022-11-03T15:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + ], + } +] successful_live_response = [ - { - "Success": True, - "Values": [ - { - "NodeId": {"Id": "SOMEID", "Namespace": 0, "IdType": 0}, - "Value": {"Type": 10, "Body": 1.2}, - "ServerTimestamp": "2022-01-01T12:00:00Z", - "StatusCode": {"Code": 0, "Symbol": "Good"} - }, - { - "NodeId": {"Id": "SOMEID2", "Namespace": 0, "IdType": 0}, - "Value": {"Type": 11, "Body": 2.3}, - "ServerTimestamp": "2022-01-01T12:05:00Z", - "StatusCode": {"Code": 1, "Symbol": "Uncertain"} - }, - ], - } - ] + { + "Success": True, + "Values": [ + { + "NodeId": {"Id": "SOMEID", "Namespace": 0, "IdType": 0}, + "Value": {"Type": 10, "Body": 1.2}, + "ServerTimestamp": "2022-01-01T12:00:00Z", + "StatusCode": {"Code": 0, "Symbol": "Good"}, + }, + { + "NodeId": {"Id": "SOMEID2", "Namespace": 0, "IdType": 0}, + "Value": {"Type": 11, "Body": 2.3}, + "ServerTimestamp": "2022-01-01T12:05:00Z", + "StatusCode": {"Code": 1, "Symbol": "Uncertain"}, + }, + ], + } +] empty_live_response = [ { @@ -211,7 +153,7 @@ }, ], }, -{ + { "NodeId": { "IdType": 2, "Id": "SOMEID2", @@ -230,8 +172,8 @@ "SourceTimestamp": "2022-09-13T14:39:51Z", }, ], - } - ] + }, + ], } successful_raw_historical_result = { @@ -283,108 +225,87 @@ "SourceTimestamp": "2022-09-13T13:41:51Z", }, ], - } - ] + }, + ], } successful_write_live_response = { "Success": True, "ErrorMessage": "string", "ErrorCode": 0, - "ServerNamespaces": [ - "string" - ], - "StatusCodes": [ - { - "Code": 0, - "Symbol": "Good" - } - ] - } - + "ServerNamespaces": ["string"], + "StatusCodes": [{"Code": 0, "Symbol": "Good"}], +} + empty_write_live_response = { "Success": True, "ErrorMessage": "string", "ErrorCode": 0, - "ServerNamespaces": [ - "string" - ], - "StatusCodes": [ - { - - } - ] - } + "ServerNamespaces": ["string"], + "StatusCodes": [{}], +} successful_write_historical_response = { - "Success": True, - "ErrorMessage": "string", - "ErrorCode": 0, - "ServerNamespaces": [ - "string" - ], - "HistoryUpdateResults": [ - { - } - ] + "Success": True, + "ErrorMessage": "string", + "ErrorCode": 0, + "ServerNamespaces": ["string"], + "HistoryUpdateResults": [{}], } unsuccessful_write_historical_response = { - "Success": True, - "ErrorMessage": "string", - "ErrorCode": 0, - "ServerNamespaces": [ - "string" - ] + "Success": True, + "ErrorMessage": "string", + "ErrorCode": 0, + "ServerNamespaces": ["string"], } successfull_write_historical_response_with_errors = { - "Success": True, - "ErrorMessage": "string", - "ErrorCode": 0, - "ServerNamespaces": [ - "string" - ], - "HistoryUpdateResults": [ - { - "StatusCode": { - 'Code': 2158690304, - 'Symbol': 'BadInvalidArgument' - } - } - ] + "Success": True, + "ErrorMessage": "string", + "ErrorCode": 0, + "ServerNamespaces": ["string"], + "HistoryUpdateResults": [ + {"StatusCode": {"Code": 2158690304, "Symbol": "BadInvalidArgument"}} + ], } + class SubValue(BaseModel): Type: int Body: float + class StatusCode(BaseModel): Code: int Symbol: str + class Value(BaseModel): Value: SubValue SourceTimestamp: str StatusCode: StatusCode + class Variables(BaseModel): Id: str Namespace: int IdType: int + class WriteHistoricalVariables(BaseModel): NodeId: Variables PerformInsertReplace: int UpdateValues: List[Value] + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code self.raise_for_status = mock.Mock(return_value=False) - self.headers = {'Content-Type': 'application/json'} + self.headers = {"Content-Type": "application/json"} def json(self): return self.json_data @@ -393,17 +314,19 @@ def json(self): # This method will be used by the mock to replace requests def successful_mocked_requests(*args, **kwargs): status_code = 200 if args[0] == f"{URL}values/get" else 404 - json_data = successful_live_response + json_data = successful_live_response response = MockResponse(json_data=json_data, status_code=status_code) response.json_data = json_data return response + def empty_values_mocked_requests(*args, **kwargs): if args[0] == f"{URL}values/get": return MockResponse(empty_live_response, 200) return MockResponse(None, 404) + def successful_write_mocked_requests(*args, **kwargs): if args[0] == f"{URL}values/set": return MockResponse(successful_write_live_response, 200) @@ -417,6 +340,7 @@ def empty_write_values_mocked_requests(*args, **kwargs): return MockResponse(None, 404) + def no_write_mocked_requests(*args, **kwargs): if args[0] == f"{URL}values/set": # Set Success to False @@ -471,6 +395,7 @@ def no_write_mocked_historical_requests(*args, **kwargs): return MockResponse(None, 404) + def successful_write_historical_with_errors_mocked_requests(*args, **kwargs): if args[0] == f"{URL}values/historicalwrite": suce = deepcopy(successfull_write_historical_response_with_errors) @@ -478,6 +403,7 @@ def successful_write_historical_with_errors_mocked_requests(*args, **kwargs): return MockResponse(None, 404) + def unsuccessful_write_historical_mocked_requests(*args, **kwargs): if args[0] == f"{URL}values/historicalwrite": # Set Success to False @@ -546,9 +472,11 @@ def empty_mocked_requests(*args, **kwargs): return MockResponse(None, 404) + class AnyUrlModel(BaseModel): url: AnyUrl + class TestOPCUA(unittest.TestCase): def test_malformed_rest_url(self): with pytest.raises(ValidationError): @@ -567,7 +495,7 @@ def test_namespaces(self): def test_get_value_type(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result_none = opc._get_value_type(100000) - assert result_none["id"] == None + assert result_none["id"] is None result = opc._get_value_type(1) assert "id" in result assert "type" in result @@ -587,7 +515,6 @@ def test_check_auth_client_is_none(self): with pytest.raises(Exception): opc.check_auth_client() - @mock.patch("requests.post", side_effect=successful_mocked_requests) def test_get_live_values_successful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -597,39 +524,56 @@ def test_get_live_values_successful(self, mock_get): assert result[num]["Id"] == list_of_ids[num]["Id"] assert ( result[num]["Timestamp"] - == successful_live_response[0]["Values"][num]["ServerTimestamp"] + == successful_live_response[0]["Values"][num][ + "ServerTimestamp" + ] ) assert ( result[num]["Value"] - == successful_live_response[0]["Values"][num]["Value"]["Body"] + == successful_live_response[0]["Values"][num]["Value"][ + "Body" + ] ) assert ( result[num]["ValueType"] == tsdata._get_value_type( - successful_live_response[0]["Values"][num]["Value"]["Type"] + successful_live_response[0]["Values"][num]["Value"][ + "Type" + ] )["type"] ) assert ( result[num]["StatusCode"] - == successful_live_response[0]["Values"][num]["StatusCode"]["Code"] + == successful_live_response[0]["Values"][num][ + "StatusCode" + ]["Code"] ) assert ( result[num]["StatusSymbol"] - == successful_live_response[0]["Values"][num]["StatusCode"]["Symbol"] + == successful_live_response[0]["Values"][num][ + "StatusCode" + ]["Symbol"] ) - @mock.patch("requests.post", side_effect=successful_mocked_requests) def test_get_live_values_successful_with_auth(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client) + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.token = Token( + session_token=auth_session_id, expires_at=auth_expires_at + ) + tsdata = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client + ) result = tsdata.get_values(list_of_ids) for num, row in enumerate(list_of_ids): assert result[num]["Id"] == list_of_ids[num]["Id"] assert ( result[num]["Timestamp"] - == successful_live_response[0]["Values"][num]["ServerTimestamp"] + == successful_live_response[0]["Values"][num][ + "ServerTimestamp" + ] ) assert ( result[num]["Value"] @@ -643,20 +587,23 @@ def test_get_live_values_successful_with_auth(self, mock_get): ) assert ( result[num]["StatusCode"] - == successful_live_response[0]["Values"][num]["StatusCode"]["Code"] + == successful_live_response[0]["Values"][num]["StatusCode"][ + "Code" + ] ) assert ( result[num]["StatusSymbol"] - == successful_live_response[0]["Values"][num]["StatusCode"]["Symbol"] + == successful_live_response[0]["Values"][num]["StatusCode"][ + "Symbol" + ] ) - @mock.patch("requests.post", side_effect=empty_values_mocked_requests) def test_get_live_values_with_missing_value_and_statuscode(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) for num, row in enumerate(list_of_ids): - if num < len(result): + if num < len(result): assert result[num]["Id"] == list_of_ids[num]["Id"] assert ( result[num]["Timestamp"] @@ -671,7 +618,7 @@ def test_get_live_values_with_missing_value_and_statuscode(self, mock_get): def test_get_live_values_no_response(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) - assert result[0]["Timestamp"] == None + assert result[0]["Timestamp"] is None @mock.patch("requests.post", side_effect=unsuccessful_mocked_requests) def test_get_live_values_unsuccessful(self, mock_post): @@ -683,13 +630,13 @@ def test_get_live_values_unsuccessful(self, mock_post): def test_get_live_values_empty(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) - assert result[0]["Timestamp"] == None + assert result[0]["Timestamp"] is None @mock.patch("requests.post", side_effect=no_status_code_mocked_requests) def test_get_live_values_no_status_code(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) - assert result[0]["StatusCode"] == None + assert result[0]["StatusCode"] is None @mock.patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): @@ -706,9 +653,13 @@ def test_write_live_values_successful(self, mock_get): ) assert result[num]["WriteSuccess"] is True - @mock.patch("requests.post", side_effect=empty_write_values_mocked_requests) - def test_write_live_values_with_missing_value_and_statuscode(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + @mock.patch( + "requests.post", side_effect=empty_write_values_mocked_requests + ) + def test_write_live_values_with_missing_value_and_statuscode( + self, mock_get + ): + tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.write_values(list_of_write_values) for num, row in enumerate(list_of_write_values): assert result[num]["WriteSuccess"] is False @@ -719,67 +670,118 @@ def test_get_write_live_values_no_response(self, mock_get): result = tsdata.write_values(list_of_write_values) assert result is None - @mock.patch("requests.post", side_effect=unsuccessful_write_mocked_requests) + @mock.patch( + "requests.post", side_effect=unsuccessful_write_mocked_requests + ) def test_get_write_live_values_unsuccessful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with pytest.raises(RuntimeError): - result = tsdata.write_values(list_of_write_values) + tsdata.write_values(list_of_write_values) @mock.patch("requests.post", side_effect=empty_write_mocked_requests) def test_get_write_live_values_empty(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with pytest.raises(ValueError): - result = tsdata.write_values(list_of_write_values) + tsdata.write_values(list_of_write_values) - @mock.patch("requests.post", side_effect=successful_write_historical_mocked_requests) + @mock.patch( + "requests.post", + side_effect=successful_write_historical_mocked_requests, + ) def test_write_historical_values_successful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values + ] result = tsdata.write_historical_values(converted_data) for num, row in enumerate(list_of_write_values): - assert result[0]["WriteSuccess"] == True + assert result[0]["WriteSuccess"] is True - @mock.patch("requests.post", side_effect=successful_write_historical_mocked_requests) + @mock.patch( + "requests.post", + side_effect=successful_write_historical_mocked_requests, + ) def test_write_wrong_order_historical_values_successful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values_in_wrong_order] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values_in_wrong_order + ] with pytest.raises(ValueError): - result = tsdata.write_historical_values(converted_data) + tsdata.write_historical_values(converted_data) - @mock.patch("requests.post", side_effect=empty_write_historical_mocked_requests) - def test_write_historical_values_with_missing_value_and_statuscode(self, mock_get): + @mock.patch( + "requests.post", side_effect=empty_write_historical_mocked_requests + ) + def test_write_historical_values_with_missing_value_and_statuscode( + self, mock_get + ): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values + ] with pytest.raises(ValueError): - result = tsdata.write_historical_values(converted_data) + tsdata.write_historical_values(converted_data) - @mock.patch("requests.post", side_effect=no_write_mocked_historical_requests) + @mock.patch( + "requests.post", side_effect=no_write_mocked_historical_requests + ) def test_get_write_historical_values_no_response(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values + ] result = tsdata.write_historical_values(converted_data) assert result is None - @mock.patch("requests.post", side_effect=unsuccessful_write_historical_mocked_requests) + @mock.patch( + "requests.post", + side_effect=unsuccessful_write_historical_mocked_requests, + ) def test_get_write_historical_values_unsuccessful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values + ] with pytest.raises(RuntimeError): - result = tsdata.write_historical_values(converted_data) + tsdata.write_historical_values(converted_data) - @mock.patch("requests.post", side_effect=successful_write_historical_with_errors_mocked_requests) - def test_get_write_historical_values_successful_with_error_codes(self, mock_get): + @mock.patch( + "requests.post", + side_effect=successful_write_historical_with_errors_mocked_requests, + ) + def test_get_write_historical_values_successful_with_error_codes( + self, mock_get + ): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [WriteHistoricalVariables(**item) for item in list_of_historical_values_wrong_type_and_value] + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_historical_values_wrong_type_and_value + ] result = tsdata.write_historical_values(converted_data) - assert result[0]["WriteError"]["Code"] == successfull_write_historical_response_with_errors["HistoryUpdateResults"][0]["StatusCode"]["Code"] - assert result[0]["WriteError"]["Symbol"] == successfull_write_historical_response_with_errors["HistoryUpdateResults"][0]["StatusCode"]["Symbol"] + assert ( + result[0]["WriteError"]["Code"] + == successfull_write_historical_response_with_errors[ + "HistoryUpdateResults" + ][0]["StatusCode"]["Code"] + ) + assert ( + result[0]["WriteError"]["Symbol"] + == successfull_write_historical_response_with_errors[ + "HistoryUpdateResults" + ][0]["StatusCode"]["Symbol"] + ) + class AsyncMockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status = status_code - self.headers = {'Content-Type': 'application/json'} + self.headers = {"Content-Type": "application/json"} async def __aenter__(self): return self @@ -789,37 +791,34 @@ async def __aexit__(self, exc_type, exc, tb): async def json(self): return self.json_data - + async def raise_for_status(self): if self.status >= 400: raise aiohttp.ClientResponseError( request_info=aiohttp.RequestInfo( - url=URL, - method="POST", - headers={}, - real_url=OPC_URL + url=URL, method="POST", headers={}, real_url=OPC_URL ), history=(), status=self.status, message="Mocked error", - headers=self.headers + headers=self.headers, ) + def unsuccessful_async_mock_response(*args, **kwargs): - return AsyncMockResponse( - json_data=None, - status_code=400 - ) + return AsyncMockResponse(json_data=None, status_code=400) + async def make_historical_request(): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) return await tsdata.get_historical_aggregated_values_asyn( - start_time=(datetime.datetime.now() - datetime.timedelta(30)), - end_time=(datetime.datetime.now() - datetime.timedelta(29)), - pro_interval=3600000, - agg_name="Average", - variable_list=list_of_ids, - ) + start_time=(datetime.datetime.now() - datetime.timedelta(30)), + end_time=(datetime.datetime.now() - datetime.timedelta(29)), + pro_interval=3600000, + agg_name="Average", + variable_list=list_of_ids, + ) + async def make_raw_historical_request(): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -829,20 +828,32 @@ async def make_raw_historical_request(): variable_list=list_of_ids, ) + @pytest.mark.asyncio class TestAsyncOPCUA: @mock.patch("aiohttp.ClientSession.post") async def test_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( - json_data=successful_historical_result, - status_code=200 + json_data=successful_historical_result, status_code=200 ) result = await make_historical_request() cols_to_check = ["Value"] - assert all(ptypes.is_numeric_dtype(result[col]) for col in cols_to_check) - assert result['Value'].tolist() == [34.28500000000003, 6.441666666666666, 34.28500000000003, 6.441666666666666] - assert result['ValueType'].tolist() == ["Double", "Double", "Double", "Double"] + assert all( + ptypes.is_numeric_dtype(result[col]) for col in cols_to_check + ) + assert result["Value"].tolist() == [ + 34.28500000000003, + 6.441666666666666, + 34.28500000000003, + 6.441666666666666, + ] + assert result["ValueType"].tolist() == [ + "Double", + "Double", + "Double", + "Double", + ] @mock.patch("aiohttp.ClientSession.post") async def test_historical_values_no_dict(self, mock_post): @@ -863,12 +874,13 @@ async def test_historical_values_no_hist(self, mock_post): @mock.patch("aiohttp.ClientSession.post") async def test_raw_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( - json_data=successful_raw_historical_result, - status_code=200 + json_data=successful_raw_historical_result, status_code=200 ) result = await make_raw_historical_request() cols_to_check = ["Value"] - assert all(ptypes.is_numeric_dtype(result[col]) for col in cols_to_check) + assert all( + ptypes.is_numeric_dtype(result[col]) for col in cols_to_check + ) @mock.patch("aiohttp.ClientSession.post") async def test_raw_historical_values_no_dict(self, mock_post): @@ -885,5 +897,6 @@ async def test_raw_historical_values_no_hist(self, mock_post): with pytest.raises(RuntimeError): await make_raw_historical_request() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_shared.py b/tests/shared_test.py similarity index 80% rename from tests/test_shared.py rename to tests/shared_test.py index 3dc1195..9d83e1d 100644 --- a/tests/test_shared.py +++ b/tests/shared_test.py @@ -2,7 +2,6 @@ from unittest import mock import requests import pytest -from pydantic import ValidationError from pyprediktormapclient.shared import request_from_api @@ -17,17 +16,18 @@ } ] + # This method will be used by the mock to replace requests def mocked_requests(*args, **kwargs): class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code - self.headers = {'Content-Type': 'application/json'} + self.headers = {"Content-Type": "application/json"} def json(self): return self.json_data - + def raise_for_status(self): return None @@ -36,18 +36,25 @@ def raise_for_status(self): return MockResponse(None, 404) + class AnalyticsHelperTestCase(unittest.TestCase): def test_requests_with_malformed_url(self): with pytest.raises(requests.exceptions.MissingSchema): - request_from_api(rest_url="No_valid_url", method="GET", endpoint="/") + request_from_api( + rest_url="No_valid_url", method="GET", endpoint="/" + ) def test_requests_with_unsupported_method(self): with pytest.raises(TypeError): - request_from_api(rest_url=URL, method="NO_SUCH_METHOD", endpoint="/") + request_from_api( + rest_url=URL, method="NO_SUCH_METHOD", endpoint="/" + ) @mock.patch("requests.get", side_effect=mocked_requests) def test_request_from_api_method_get(self, mock_get): - result = request_from_api(rest_url=URL, method="GET", endpoint="something") + result = request_from_api( + rest_url=URL, method="GET", endpoint="something" + ) assert result == return_json @mock.patch("requests.post", side_effect=mocked_requests) diff --git a/tests/test_auth_client.py b/tests/test_auth_client.py deleted file mode 100644 index 6d4ff90..0000000 --- a/tests/test_auth_client.py +++ /dev/null @@ -1,427 +0,0 @@ -import unittest -from unittest import mock -import pytest -from pydantic import ValidationError, BaseModel, AnyUrl -from copy import deepcopy -import datetime - -from pyprediktormapclient.auth_client import AUTH_CLIENT, Token - -URL = "http://someserver.somedomain.com/v1/" -username = "some@user.com" -password = "somepassword" -auth_id = "0b518533-fb09-4bb7-a51f-166d3453685e" -auth_session_id = "qlZULxcaNc6xVdXQfqPxwix5v3tuCLaO" -auth_expires_at = "2022-12-04T07:31:28.767407252Z" -auth_expires_at_2hrs_ago = "2022-12-04T05:31:28.767407252Z" - -successful_self_service_login_token_mocked_response = { - "Success": True, - "ErrorMessage": "", - "ErrorCode": 0, - "session_token": "qlZULxcaNc6xVdXQfqPxwix5v3tuCLaO", - "session": { - "id": "b26e9032-c34b-4f14-a912-c055b12f02da", - "active": True, - "expires_at": "2022-12-04T07:31:28.767407252Z", - "authenticated_at": "2022-12-01T07:31:28.767407252Z", - "authenticator_assurance_level": "aal1", - "authentication_methods": [ - { - "method": "password", - "aal": "aal1", - "completed_at": "2022-12-01T07:31:28.767403652Z" - } - ], - "issued_at": "2022-12-01T07:31:28.767407252Z", - "identity": { - "id": "24f9466f-4e81-42d8-8a40-1f46f2203c19", - "schema_id": "preset://email", - "schema_url": "https://authauth.blabla.io/schemas/cHJlc2V0Oi8vZW1haWw", - "state": "active", - "state_changed_at": "2022-08-15T12:43:28.623721Z", - "traits": { - "email": "api@prediktor.com" - }, - "verifiable_addresses": [ - { - "id": "c083c747-eee9-4ee0-a7b9-96aa067bcea6", - "value": "api@prediktor.com", - "verified": False, - "via": "email", - "status": "sent", - "created_at": "2022-08-15T12:43:28.631351Z", - "updated_at": "2022-08-15T12:43:28.631351Z" - } - ], - "recovery_addresses": [ - { - "id": "f907259d-52d9-420b-985a-004b6e745bd0", - "value": "api@prediktor.com", - "via": "email", - "created_at": "2022-08-15T12:43:28.637771Z", - "updated_at": "2022-08-15T12:43:28.637771Z" - } - ], - "metadata_public": None, - "created_at": "2022-08-15T12:43:28.626258Z", - "updated_at": "2022-08-15T12:43:28.626258Z" - }, - "devices": [ - { - "id": "5f805841-d4fc-461f-ad1b-87ca4166355d", - "ip_address": "81.166.54.38", - "user_agent": "Go-http-client/1.1", - "location": "Fredrikstad, NO" - } - ] - } - } - -# TODO handle old id to get new token, use use_flow_id, investigate if this works -expired_self_service_logon_response = { - "error": { - "id": "self_service_flow_expired", - "code": 410, - "status": "Gone", - "reason": "The self-service flow expired 904.65 minutes ago, initialize a new one.", - "message": "self-service flow expired" - }, - "expired_at": "2022-11-24T15:37:59.690422Z", - "since": 54279099192303, - "use_flow_id": "2b132a0d-f3a5-4eff-8a1b-f785228168a9" - } - - - -successfull_self_service_login_response ={ - "Success": True, - "ErrorMessage": "", - "ErrorCode": 0, - "id": "0b518533-fb09-4bb7-a51f-166d3453685e", - "oauth2_login_challenge": None, - "type": "api", - "expires_at": "2022-11-24T15:37:59.690422428Z", - "issued_at": "2022-11-24T14:37:59.690422428Z", - "request_url": "https://authauth.blabla.io/self-service/login/api", - "ui": { - "action": "https://authauth.blabla.io/self-service/login?flow=0b518533-fb09-4bb7-a51f-166d3453685e", - "method": "POST", - "nodes": [ - { - "type": "input", - "group": "default", - "attributes": { - "name": "csrf_token", - "type": "hidden", - "value": "", - "required": True, - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": {} - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "identifier", - "type": "text", - "value": "", - "required": True, - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070004, - "text": "ID", - "type": "info" - } - } - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "password", - "type": "password", - "required": True, - "autocomplete": "current-password", - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070001, - "text": "Password", - "type": "info" - } - } - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "method", - "type": "submit", - "value": "password", - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1010001, - "text": "Sign in", - "type": "info", - "context": {} - } - } - } - ] - }, - "created_at": "2022-11-24T14:37:59.770365Z", - "updated_at": "2022-11-24T14:37:59.770365Z", - "refresh": False, - "requested_aal": "aal1" - } -empty_self_service_login_response = { - "Success": True, - "ErrorMessage": "", - "ErrorCode": 0, - "oauth2_login_challenge": None, - "type": "api", - "expires_at": "2022-11-24T15:37:59.690422428Z", - "issued_at": "2022-11-24T14:37:59.690422428Z", - "request_url": "https://authauth.blabla.io/self-service/login/api", - "ui": { - "method": "POST", - "nodes": [ - { - "type": "input", - "group": "default", - "attributes": { - "name": "csrf_token", - "type": "hidden", - "value": "", - "required": True, - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": {} - }, - { - "type": "input", - "group": "default", - "attributes": { - "name": "identifier", - "type": "text", - "value": "", - "required": True, - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070004, - "text": "ID", - "type": "info" - } - } - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "password", - "type": "password", - "required": True, - "autocomplete": "current-password", - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1070001, - "text": "Password", - "type": "info" - } - } - }, - { - "type": "input", - "group": "password", - "attributes": { - "name": "method", - "type": "submit", - "value": "password", - "disabled": False, - "node_type": "input" - }, - "messages": [], - "meta": { - "label": { - "id": 1010001, - "text": "Sign in", - "type": "info", - "context": {} - } - } - } - ] - }, - "created_at": "2022-11-24T14:37:59.770365Z", - "updated_at": "2022-11-24T14:37:59.770365Z", - "refresh": False, - "requested_aal": "aal1" - } - -class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - self.raise_for_status = mock.Mock(return_value=False) - self.headers = {'Content-Type': 'application/json'} - - def json(self): - return self.json_data - - -# This method will be used by the mock to replace requests -def successful_self_service_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login/api": - return MockResponse(successfull_self_service_login_response, 200) - return MockResponse(None, 404) - - -def empty_self_service_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login/api": - return MockResponse(empty_self_service_login_response, 200) - return MockResponse(None, 404) - -def unsuccessful_self_service_login_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login/api": - # Set Success to False - unsuc = deepcopy(successfull_self_service_login_response) - unsuc["Success"] = False - return MockResponse(unsuc, 200) - - return MockResponse(None, 404) - -def wrong_id_self_service_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login/api": - unsuc = deepcopy(successfull_self_service_login_response) - unsuc["id"] = 33 - return MockResponse(unsuc, 200) - - return MockResponse(None, 404) - -def successful_self_service_login_token_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login": - return MockResponse(successful_self_service_login_token_mocked_response, 200) - - return MockResponse(None, 404) - -def unsuccessful_self_service_login_token_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login": - unsuc = deepcopy(successful_self_service_login_token_mocked_response) - unsuc["Success"] = False - return MockResponse(unsuc, 200) - - return MockResponse(None, 404) - -def empty_self_service_login_token_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login": - return MockResponse(empty_self_service_login_response, 200) - - return MockResponse(None, 404) - - - - -def expired_self_service_login_mocked_requests(*args, **kwargs): - if args[0] == f"{URL}self-service/login": - return MockResponse(expired_self_service_login_mocked_requests, 200) - - return MockResponse(None, 404) - -class AnyUrlModel(BaseModel): - url: AnyUrl - -# Our test case class -class OPCUATestCase(unittest.TestCase): - - def test_malformed_rest_url(self): - with pytest.raises(ValidationError): - AnyUrlModel(rest_url="invalid-url") - - @mock.patch("requests.get", side_effect=successful_self_service_mocked_requests) - def test_get_self_service_login_id_successful(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.get_login_id() - assert auth_client.id == auth_id - - @mock.patch("requests.get", side_effect=unsuccessful_self_service_login_mocked_requests) - def test_get_self_service_login_id_successful(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - with pytest.raises(RuntimeError): - auth_client.get_login_id() - - @mock.patch("requests.get", side_effect=empty_self_service_mocked_requests) - def test_get_self_service_login_id_empty(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - with pytest.raises(RuntimeError): - auth_client.get_login_id() - - @mock.patch("requests.get", side_effect=wrong_id_self_service_mocked_requests) - def test_get_self_service_login_id_wrong_id(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - with pytest.raises(RuntimeError): - auth_client.get_login_id() - - @mock.patch("requests.post", side_effect=empty_self_service_login_token_mocked_requests) - def test_get_self_service_login_token_empty(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.id = auth_id - with pytest.raises(RuntimeError): - auth_client.get_login_token() - - @mock.patch("requests.post", side_effect=successful_self_service_login_token_mocked_requests) - def test_get_self_service_login_token_successful(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.id = auth_id - auth_client.get_login_token() - test_token = Token(session_token=auth_session_id, expires_at=auth_expires_at) - assert auth_client.token.session_token == test_token.session_token - assert auth_client.token.expires_at == test_token.expires_at - - @mock.patch("requests.post", side_effect=unsuccessful_self_service_login_token_mocked_requests) - def test_get_self_service_login_token_unsuccessful(self, mock_get): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.id = auth_id - with pytest.raises(RuntimeError): - auth_client.get_login_token() - - - def test_get_self_service_token_expired(self): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at_2hrs_ago) - auth_client.token.expires_at = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=2) - token_expired = auth_client.check_if_token_has_expired() - assert token_expired == True - - def test_get_self_service_token_expired_none(self): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - auth_client.token = Token(session_token=auth_session_id) - token_expired = auth_client.check_if_token_has_expired() - assert token_expired == True - -if __name__ == "__main__": - unittest.main() diff --git a/tox.ini b/tox.ini index 5763dcb..cd34945 100644 --- a/tox.ini +++ b/tox.ini @@ -57,22 +57,22 @@ allowlist_externals = sh echo commands_pre = - # This command creats a file named .current_version in the {toxworkdir} directory - # (the working directory for tox) and writes the current version into it, - # as determined by git describe --tags --abbrev=0. This command generates - # the most recent tag that points to a commit in the repository, + # This command creats a file named .current_version in the {toxworkdir} directory + # (the working directory for tox) and writes the current version into it, + # as determined by git describe --tags --abbrev=0. This command generates + # the most recent tag that points to a commit in the repository, # effectively capturing the latest version of your code based on your git tags. build: sh -c 'echo "SETUPTOOLS_SCM_PRETEND_VERSION=$(git describe --tags --abbrev=0)" > {toxworkdir}/.current_version' commands = clean: python -c 'import shutil; [shutil.rmtree(p, True) for p in ("build", "dist", "docs/_build")]' clean: python -c 'import pathlib, shutil; [shutil.rmtree(p, True) for p in pathlib.Path("src").glob("*.egg-info")]' # - # When the build: sh ... command runs, it reads the version from this .current_version file - # and exports it into the environment as SETUPTOOLS_SCM_PRETEND_VERSION, - # which is then used to override the version that setuptools_scm would normally + # When the build: sh ... command runs, it reads the version from this .current_version file + # and exports it into the environment as SETUPTOOLS_SCM_PRETEND_VERSION, + # which is then used to override the version that setuptools_scm would normally # automatically generate. # - # In that way, the version that is used to build the package is the same as the latest tag + # In that way, the version that is used to build the package is the same as the latest tag # which points to a commit in the repository and allows us to successfully deploy on PyPi. build: sh -c 'export $(cat {toxworkdir}/.current_version) && python -m build {posargs}' From bf21fbed28507e56cb19e6408a10ee0af9039949 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Wed, 28 Aug 2024 11:27:42 +0200 Subject: [PATCH 03/35] Fixed TypeError in failed tests. --- src/pyprediktormapclient/analytics_helper.py | 6 ++++-- tests/analytics_helper_test.py | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pyprediktormapclient/analytics_helper.py b/src/pyprediktormapclient/analytics_helper.py index 2bb6b88..f8c47a7 100644 --- a/src/pyprediktormapclient/analytics_helper.py +++ b/src/pyprediktormapclient/analytics_helper.py @@ -1,7 +1,8 @@ import pandas as pd import logging +import re from typing import List, Any -from pydantic import validate_call, constr +from pydantic import validate_call logger = logging.getLogger(__name__) @@ -123,7 +124,8 @@ def namespaces_as_list(self, list_of_dicts: List) -> List: @validate_call def split_id(self, id: Any) -> dict: - if not isinstance(id, constr(pattern=r"^\d+:\d+:\S+$")): + pattern = r"^\d+:\d+:\S+$" + if not re.match(pattern, id): raise ValueError("Invalid id format") id_split = id.split(":") diff --git a/tests/analytics_helper_test.py b/tests/analytics_helper_test.py index 0898ae3..21362af 100644 --- a/tests/analytics_helper_test.py +++ b/tests/analytics_helper_test.py @@ -1,7 +1,6 @@ import unittest import pandas as pd import pytest -from pydantic import ValidationError from pyprediktormapclient.analytics_helper import AnalyticsHelper @@ -103,7 +102,7 @@ def test_split_id_success(self): def test_split_id_failure(self): result = AnalyticsHelper(proper_json) - with pytest.raises(ValidationError): + with pytest.raises(ValueError): result.split_id("TEXT:TEXT:TEXT") def test_analytics_helper_descendants_success(self): From d687c4344390386dc9664f56038a58d9171a3269 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Thu, 29 Aug 2024 09:52:23 +0200 Subject: [PATCH 04/35] Added webhook setup in GitHub Actions workflow. --- .github/workflows/ci-cd-webhook.yml | 58 +++++++++++++++++++++++++++++ tests/auth_client_test.py | 39 +++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/.github/workflows/ci-cd-webhook.yml b/.github/workflows/ci-cd-webhook.yml index 978da6c..db3e118 100644 --- a/.github/workflows/ci-cd-webhook.yml +++ b/.github/workflows/ci-cd-webhook.yml @@ -26,3 +26,61 @@ jobs: - name: Run pre-commit checks run: pre-commit run --all-files +webhook: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Google Cloud CLI + run: | + echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list + sudo apt-get install apt-transport-https ca-certificates gnupg + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - + sudo apt-get update && sudo apt-get install google-cloud-cli + + - name: Authenticate with Google Cloud + run: | + echo '${{ secrets.GCP_SA_KEY }}' > sa-key.json + gcloud auth activate-service-account sa-git-data-sources@tgs-prediktor-dev-598e.iam.gserviceaccount.com --key-file=sa-key.json + rm sa-key.json + + - name: Generate and send identity token + run: | + IDENTITY_TOKEN=$(gcloud auth print-identity-token --audiences="https://us-central1-tgs-prediktor-dev-598e.cloudfunctions.net/git_webhook_to_bigquery") + WEBHOOK_URL="https://us-central1-tgs-prediktor-dev-598e.cloudfunctions.net/git_webhook_to_bigquery" + + prepare_payload() { + local event_type="$1" + jq -n \ + --arg event "$event_type" \ + --arg repository "${{ github.repository }}" \ + --arg url "https://github.com/${{ github.repository }}" \ + --arg actor "${{ github.actor }}" \ + --arg ref "${{ github.ref }}" \ + --argjson payload "$2" \ + '{ + event: $event, + repository: { name: $repository, url: $url }, + actor: $actor, + ref: $ref, + payload: $payload + }' + } + + case "${{ github.event_name }}" in + push) + PAYLOAD=$(prepare_payload "push" "$(echo '${{ toJson(github.event) }}' | jq '{commits: .commits, before: .before, after: .after, pusher: .pusher}')") + ;; + pull_request) + PAYLOAD=$(prepare_payload "pull_request" "$(echo '${{ toJson(github.event.pull_request) }}' | jq '{number: .number, title: .title, state: .state, body: .body}')") + ;; + *) + PAYLOAD=$(prepare_payload "${{ github.event_name }}" "{}") + ;; + esac + + curl -H "Authorization: Bearer $IDENTITY_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + $WEBHOOK_URL \ No newline at end of file diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index c706df6..e72a764 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -1,9 +1,11 @@ import unittest from unittest import mock +from unittest.mock import patch import pytest from pydantic import ValidationError, BaseModel, AnyUrl from copy import deepcopy import datetime +import requests from pyprediktormapclient.auth_client import AUTH_CLIENT, Token @@ -355,6 +357,17 @@ class AnyUrlModel(BaseModel): # Our test case class class OPCUATestCase(unittest.TestCase): + def test_init(self): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + assert auth_client.rest_url == URL + assert auth_client.username == username + assert auth_client.password == password + assert auth_client.id == None + assert auth_client.headers == {"Content-Type": "application/json"} + assert isinstance(auth_client.session, requests.Session) + def test_malformed_rest_url(self): with pytest.raises(ValidationError): AnyUrlModel(rest_url="invalid-url") @@ -459,6 +472,32 @@ def test_get_self_service_token_expired_none(self): token_expired = auth_client.check_if_token_has_expired() assert token_expired + @patch.object(AUTH_CLIENT, 'get_login_id') + @patch.object(AUTH_CLIENT, 'get_login_token') + def test_request_new_ory_token(self, mock_get_login_token, mock_get_login_id): + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_client.request_new_ory_token() + mock_get_login_id.assert_called_once() + mock_get_login_token.assert_called_once() + + def test_token_remove_nanoseconds(self): + + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + + auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) + assert auth_client.token.expires_at == datetime.datetime(2022, 12, 4, 0, 0, 0, 767407252, tzinfo=datetime.timezone.utc) + + auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) + assert auth_client.token.expires_at is None + + invalid_datetime = "2024-08-29 00:00:00" + auth_client.token = Token(session_token=auth_session_id, expires_at=invalid_datetime) + assert auth_client.token.expires_at == invalid_datetime + if __name__ == "__main__": unittest.main() From f85d82b3537822f1df33bce20e9171618213c6d7 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Thu, 29 Aug 2024 09:59:33 +0200 Subject: [PATCH 05/35] Corrected webhook alignment. --- .github/workflows/ci-cd-webhook.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd-webhook.yml b/.github/workflows/ci-cd-webhook.yml index db3e118..0ac8ca9 100644 --- a/.github/workflows/ci-cd-webhook.yml +++ b/.github/workflows/ci-cd-webhook.yml @@ -26,7 +26,8 @@ jobs: - name: Run pre-commit checks run: pre-commit run --all-files -webhook: + + webhook: runs-on: ubuntu-latest steps: - name: Checkout code From c951ecb0f6d9ebf29216b10400cf34e2309c6253 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Thu, 29 Aug 2024 15:54:27 +0200 Subject: [PATCH 06/35] Refactored and optimized unit tests in shared_test.py and auth_client.py. --- src/pyprediktormapclient/auth_client.py | 18 ++-- tests/auth_client_test.py | 123 ++++++++++++++++++++++-- tests/shared_test.py | 112 ++++++++++++++++++--- 3 files changed, 224 insertions(+), 29 deletions(-) diff --git a/src/pyprediktormapclient/auth_client.py b/src/pyprediktormapclient/auth_client.py index fbd0eed..8cc4efd 100644 --- a/src/pyprediktormapclient/auth_client.py +++ b/src/pyprediktormapclient/auth_client.py @@ -1,4 +1,5 @@ -from pydantic import BaseModel, AnyUrl, AwareDatetime, field_validator +from pydantic import BaseModel, AnyUrl, field_validator, ConfigDict +from typing import Optional from pyprediktormapclient.shared import request_from_api import datetime import requests @@ -14,12 +15,16 @@ class Ory_Login_Structure(BaseModel): class Token(BaseModel): session_token: str - expires_at: AwareDatetime = None + expires_at: Optional[datetime.datetime] = None + + model_config = ConfigDict(arbitrary_types_allowed=True) @field_validator("expires_at", mode="before") def remove_nanoseconds(cls, v): if v is None: return v + if isinstance(v, datetime.datetime): + return v match = re.match( r"(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d).\d+(\S+)", v ) @@ -108,14 +113,13 @@ def get_login_token(self) -> None: self.token = Token(session_token=content.get("session_token")) # Check if token has expiry date, save it if it does - if isinstance(content.get("session").get("expires_at"), str): - # String returned from ory has to many chars in microsec. Remove them - # from_string = content.get("session").get("expires_at") - # date_object = datetime.datetime.strptime(f"{from_string[:-11]}.+00:00", "%Y-%m-%dT%H:%M:%S.%z") + expires_at_str = content.get("session", {}).get("expires_at") + if isinstance(expires_at_str, str): try: + expires_at = datetime.datetime.fromisoformat(expires_at_str.replace("Z", "+00:00")) self.token = Token( session_token=self.token.session_token, - expires_at=content.get("session").get("expires_at"), + expires_at=expires_at, ) except Exception: # If string returned from Ory cant be parsed, still should be possible to use Ory, diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index e72a764..8fc6bbf 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -4,7 +4,7 @@ import pytest from pydantic import ValidationError, BaseModel, AnyUrl from copy import deepcopy -import datetime +from datetime import datetime, timedelta, timezone import requests from pyprediktormapclient.auth_client import AUTH_CLIENT, Token @@ -364,7 +364,34 @@ def test_init(self): assert auth_client.rest_url == URL assert auth_client.username == username assert auth_client.password == password - assert auth_client.id == None + assert auth_client.id is None + assert auth_client.token is None + assert auth_client.headers == {"Content-Type": "application/json"} + assert isinstance(auth_client.session, requests.Session) + + def test_init_with_trailing_slash(self): + url_with_trailing_slash = URL.rstrip('/') + '/' + auth_client = AUTH_CLIENT( + rest_url=url_with_trailing_slash, username=username, password=password + ) + assert auth_client.rest_url == URL + assert auth_client.username == username + assert auth_client.password == password + assert auth_client.id is None + assert auth_client.token is None + assert auth_client.headers == {"Content-Type": "application/json"} + assert isinstance(auth_client.session, requests.Session) + + def test_init_without_trailing_slash(self): + url_without_trailing_slash = URL.rstrip('/') + auth_client = AUTH_CLIENT( + rest_url=url_without_trailing_slash, username=username, password=password + ) + assert auth_client.rest_url == url_without_trailing_slash + assert auth_client.username == username + assert auth_client.password == password + assert auth_client.id is None + assert auth_client.token is None assert auth_client.headers == {"Content-Type": "application/json"} assert isinstance(auth_client.session, requests.Session) @@ -411,6 +438,44 @@ def test_get_self_service_login_id_wrong_id(self, mock_get): with pytest.raises(RuntimeError): auth_client.get_login_id() + @patch('pyprediktormapclient.auth_client.request_from_api') + def test_get_login_id_unsuccessful(self, mock_request): + # Mock the request_from_api to return an unsuccessful response + mock_request.return_value = { + "Success": False, + "ErrorMessage": "Custom error message", + "id": None + } + + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + + with self.assertRaises(RuntimeError) as context: + auth_client.get_login_id() + + self.assertEqual(str(context.exception), "Custom error message") + + @patch('pyprediktormapclient.auth_client.request_from_api') + def test_get_login_id_no_error_message(self, mock_request): + # Mock the request_from_api to return an unsuccessful response without an error message + mock_request.return_value = { + "Success": False, + "id": None + } + + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + + with self.assertRaises(RuntimeError) as context: + auth_client.get_login_id() + + self.assertEqual(str(context.exception), "Unknown error occurred during login.") + + @patch('pyprediktormapclient.auth_client.request_from_api') + def test_get_login_id_error_response(self, mock_request): + mock_request.return_value = {"error": "Some error occurred"} + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + with self.assertRaises(RuntimeError): + auth_client.get_login_id() + @mock.patch( "requests.post", side_effect=empty_self_service_login_token_mocked_requests, @@ -423,6 +488,10 @@ def test_get_self_service_login_token_empty(self, mock_get): with pytest.raises(RuntimeError): auth_client.get_login_token() + def test_check_if_token_has_expired_no_token(self): + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + self.assertTrue(auth_client.check_if_token_has_expired()) + @mock.patch( "requests.post", side_effect=successful_self_service_login_token_mocked_requests, @@ -458,12 +527,48 @@ def test_get_self_service_token_expired(self): auth_client.token = Token( session_token=auth_session_id, expires_at=auth_expires_at_2hrs_ago ) - auth_client.token.expires_at = datetime.datetime.now( - datetime.timezone.utc - ) - datetime.timedelta(hours=2) + auth_client.token.expires_at = datetime.now( + timezone.utc + ) - timedelta(hours=2) token_expired = auth_client.check_if_token_has_expired() assert token_expired + @patch('pyprediktormapclient.auth_client.request_from_api') + def test_get_login_token_success_no_expiry(self, mock_request): + mock_response = { + "Success": True, + "session_token": "some_token", + "session": {} + } + mock_request.return_value = mock_response + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + auth_client.id = "some_id" + auth_client.get_login_token() + self.assertEqual(auth_client.token.session_token, "some_token") + self.assertIsNone(auth_client.token.expires_at) + + def test_check_if_token_has_expired_valid(self): + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + future_time = datetime.now(timezone.utc) + timedelta(hours=1) + auth_client.token = Token(session_token=auth_session_id, expires_at=future_time) + self.assertFalse(auth_client.check_if_token_has_expired()) + + @patch('pyprediktormapclient.auth_client.request_from_api') + def test_get_login_token_invalid_expiry_format(self, mock_request): + mock_response = { + "Success": True, + "session_token": "some_token", + "session": { + "expires_at": "invalid_date_format" + } + } + mock_request.return_value = mock_response + auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + auth_client.id = "some_id" + auth_client.get_login_token() + self.assertEqual(auth_client.token.session_token, "some_token") + self.assertIsNone(auth_client.token.expires_at) + def test_get_self_service_token_expired_none(self): auth_client = AUTH_CLIENT( rest_url=URL, username=username, password=password @@ -488,13 +593,15 @@ def test_token_remove_nanoseconds(self): rest_url=URL, username=username, password=password ) - auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) - assert auth_client.token.expires_at == datetime.datetime(2022, 12, 4, 0, 0, 0, 767407252, tzinfo=datetime.timezone.utc) + auth_expires_at_datetime = datetime(2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc) auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) + assert auth_client.token.expires_at == auth_expires_at_datetime + + auth_client.token = Token(session_token=auth_session_id, expires_at=None) assert auth_client.token.expires_at is None - invalid_datetime = "2024-08-29 00:00:00" + invalid_datetime = datetime(2024, 8, 29, 0, 0, tzinfo=timezone.utc) auth_client.token = Token(session_token=auth_session_id, expires_at=invalid_datetime) assert auth_client.token.expires_at == invalid_datetime diff --git a/tests/shared_test.py b/tests/shared_test.py index 9d83e1d..781b451 100644 --- a/tests/shared_test.py +++ b/tests/shared_test.py @@ -2,7 +2,7 @@ from unittest import mock import requests import pytest - +from requests.exceptions import RequestException from pyprediktormapclient.shared import request_from_api URL = "http://someserver.somedomain.com/v1/" @@ -17,23 +17,23 @@ ] -# This method will be used by the mock to replace requests -def mocked_requests(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - self.headers = {"Content-Type": "application/json"} +class MockResponse: + def __init__(self, json_data, status_code, headers=None): + self.json_data = json_data + self.status_code = status_code + self.headers = headers or {} + self.text = str(json_data) - def json(self): - return self.json_data + def json(self): + return self.json_data - def raise_for_status(self): - return None + def raise_for_status(self): + if self.status_code >= 400: + raise RequestException(f"HTTP Error: {self.status_code}") +def mocked_requests(*args, **kwargs): if args[0] == f"{URL}something": - return MockResponse(return_json, 200) - + return MockResponse(return_json, 200, {"Content-Type": "application/json"}) return MockResponse(None, 404) @@ -64,6 +64,90 @@ def test_request_from_api_method_post(self, mock_get): ) assert result == return_json + @mock.patch("requests.get") + def test_request_from_api_json_response(self, mock_get): + json_response = {"key": "value"} + mock_get.return_value = MockResponse(json_response, 200, headers={"Content-Type": "application/json"}) + + result = request_from_api( + rest_url=URL, + method="GET", + endpoint="test" + ) + + self.assertEqual(result, json_response) + + @mock.patch("requests.get") + def test_request_from_api_non_json_response(self, mock_get): + non_json_response = "This is a plain text response" + mock_get.return_value = MockResponse(non_json_response, 200, headers={"Content-Type": "text/plain"}) + + result = request_from_api( + rest_url=URL, + method="GET", + endpoint="test" + ) + + self.assertEqual(result, {"error": "Non-JSON response", "content": non_json_response}) + mock_get.assert_called_once_with(f"{URL}test", timeout=(3, 27), params=None, headers=None) + + @mock.patch("requests.get") + def test_request_from_api_exception(self, mock_get): + mock_get.side_effect = RequestException("Network error") + with self.assertRaises(RequestException): + request_from_api( + rest_url=URL, + method="GET", + endpoint="test" + ) + + @mock.patch("requests.get") + def test_request_from_api_extended_timeout(self, mock_get): + mock_get.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) + result = request_from_api(rest_url=URL, method="GET", endpoint="something", extended_timeout=True) + mock_get.assert_called_with(f"{URL}something", timeout=(3, 300), params=None, headers=None) + assert result == return_json + + @mock.patch("requests.Session") + def test_request_from_api_with_session(self, mock_session): + mock_session_instance = mock_session.return_value + mock_session_instance.get.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) + + session = requests.Session() + result = request_from_api(rest_url=URL, method="GET", endpoint="something", session=session) + + mock_session_instance.get.assert_called_with(f"{URL}something", timeout=(3, 27), params=None, headers=None) + assert result == return_json + + @mock.patch("requests.post") + def test_request_from_api_with_params_and_headers(self, mock_post): + mock_post.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) + params = {"param": "value"} + headers = {"Authorization": "Bearer token"} + + result = request_from_api( + rest_url=URL, + method="POST", + endpoint="something", + data="test_data", + params=params, + headers=headers + ) + mock_post.assert_called_with( + f"{URL}something", + data="test_data", + headers=headers, + timeout=(3, 27), + params=params + ) + assert result == return_json + + @mock.patch("requests.get") + def test_request_from_api_http_error(self, mock_get): + mock_get.return_value = MockResponse(None, 404) + with pytest.raises(RequestException): + request_from_api(rest_url=URL, method="GET", endpoint="something") + if __name__ == "__main__": unittest.main() From 39a6e52c26e08840c4d272b95cac470e768517a0 Mon Sep 17 00:00:00 2001 From: MeenaBana Date: Fri, 30 Aug 2024 10:45:09 +0200 Subject: [PATCH 07/35] Refactored and updated remaining tests in auth_client_test.py --- .github/workflows/ci-cd-webhook.yml | 2 +- src/pyprediktormapclient/auth_client.py | 4 +- tests/auth_client_test.py | 159 +++++++++++------------- tests/shared_test.py | 79 +++++++----- 4 files changed, 122 insertions(+), 122 deletions(-) diff --git a/.github/workflows/ci-cd-webhook.yml b/.github/workflows/ci-cd-webhook.yml index 0ac8ca9..ddf40b0 100644 --- a/.github/workflows/ci-cd-webhook.yml +++ b/.github/workflows/ci-cd-webhook.yml @@ -84,4 +84,4 @@ jobs: curl -H "Authorization: Bearer $IDENTITY_TOKEN" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ - $WEBHOOK_URL \ No newline at end of file + $WEBHOOK_URL diff --git a/src/pyprediktormapclient/auth_client.py b/src/pyprediktormapclient/auth_client.py index 8cc4efd..ed9765b 100644 --- a/src/pyprediktormapclient/auth_client.py +++ b/src/pyprediktormapclient/auth_client.py @@ -116,7 +116,9 @@ def get_login_token(self) -> None: expires_at_str = content.get("session", {}).get("expires_at") if isinstance(expires_at_str, str): try: - expires_at = datetime.datetime.fromisoformat(expires_at_str.replace("Z", "+00:00")) + expires_at = datetime.datetime.fromisoformat( + expires_at_str.replace("Z", "+00:00") + ) self.token = Token( session_token=self.token.session_token, expires_at=expires_at, diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 8fc6bbf..7757ffb 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -355,45 +355,38 @@ class AnyUrlModel(BaseModel): # Our test case class -class OPCUATestCase(unittest.TestCase): +class TestCaseAuthClient(unittest.TestCase): def test_init(self): auth_client = AUTH_CLIENT( rest_url=URL, username=username, password=password ) - assert auth_client.rest_url == URL - assert auth_client.username == username - assert auth_client.password == password - assert auth_client.id is None - assert auth_client.token is None - assert auth_client.headers == {"Content-Type": "application/json"} - assert isinstance(auth_client.session, requests.Session) + self.assertEqual(auth_client.username, username) + self.assertEqual(auth_client.password, password) + self.assertIsNone(auth_client.id) + self.assertIsNone(auth_client.token) + self.assertEqual( + auth_client.headers, {"Content-Type": "application/json"} + ) + self.assertIsInstance(auth_client.session, requests.Session) def test_init_with_trailing_slash(self): - url_with_trailing_slash = URL.rstrip('/') + '/' + url_with_trailing_slash = URL.rstrip("/") + "/" auth_client = AUTH_CLIENT( - rest_url=url_with_trailing_slash, username=username, password=password + rest_url=url_with_trailing_slash, + username=username, + password=password, ) assert auth_client.rest_url == URL - assert auth_client.username == username - assert auth_client.password == password - assert auth_client.id is None - assert auth_client.token is None - assert auth_client.headers == {"Content-Type": "application/json"} - assert isinstance(auth_client.session, requests.Session) def test_init_without_trailing_slash(self): - url_without_trailing_slash = URL.rstrip('/') + url_without_trailing_slash = URL.rstrip("/") auth_client = AUTH_CLIENT( - rest_url=url_without_trailing_slash, username=username, password=password + rest_url=url_without_trailing_slash, + username=username, + password=password, ) assert auth_client.rest_url == url_without_trailing_slash - assert auth_client.username == username - assert auth_client.password == password - assert auth_client.id is None - assert auth_client.token is None - assert auth_client.headers == {"Content-Type": "application/json"} - assert isinstance(auth_client.session, requests.Session) def test_malformed_rest_url(self): with pytest.raises(ValidationError): @@ -438,43 +431,16 @@ def test_get_self_service_login_id_wrong_id(self, mock_get): with pytest.raises(RuntimeError): auth_client.get_login_id() - @patch('pyprediktormapclient.auth_client.request_from_api') - def test_get_login_id_unsuccessful(self, mock_request): - # Mock the request_from_api to return an unsuccessful response - mock_request.return_value = { - "Success": False, - "ErrorMessage": "Custom error message", - "id": None - } - - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - - with self.assertRaises(RuntimeError) as context: - auth_client.get_login_id() - - self.assertEqual(str(context.exception), "Custom error message") - - @patch('pyprediktormapclient.auth_client.request_from_api') + @patch("pyprediktormapclient.auth_client.request_from_api") def test_get_login_id_no_error_message(self, mock_request): - # Mock the request_from_api to return an unsuccessful response without an error message - mock_request.return_value = { - "Success": False, - "id": None - } - - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - + mock_request.return_value = {"error": "Invalid request"} + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) with self.assertRaises(RuntimeError) as context: auth_client.get_login_id() - - self.assertEqual(str(context.exception), "Unknown error occurred during login.") - - @patch('pyprediktormapclient.auth_client.request_from_api') - def test_get_login_id_error_response(self, mock_request): - mock_request.return_value = {"error": "Some error occurred"} - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - with self.assertRaises(RuntimeError): - auth_client.get_login_id() + + self.assertEqual(str(context.exception), "Invalid request") @mock.patch( "requests.post", @@ -488,10 +454,6 @@ def test_get_self_service_login_token_empty(self, mock_get): with pytest.raises(RuntimeError): auth_client.get_login_token() - def test_check_if_token_has_expired_no_token(self): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - self.assertTrue(auth_client.check_if_token_has_expired()) - @mock.patch( "requests.post", side_effect=successful_self_service_login_token_mocked_requests, @@ -527,43 +489,39 @@ def test_get_self_service_token_expired(self): auth_client.token = Token( session_token=auth_session_id, expires_at=auth_expires_at_2hrs_ago ) - auth_client.token.expires_at = datetime.now( - timezone.utc - ) - timedelta(hours=2) + auth_client.token.expires_at = datetime.now(timezone.utc) - timedelta( + hours=2 + ) token_expired = auth_client.check_if_token_has_expired() assert token_expired - @patch('pyprediktormapclient.auth_client.request_from_api') + @patch("pyprediktormapclient.auth_client.request_from_api") def test_get_login_token_success_no_expiry(self, mock_request): mock_response = { "Success": True, "session_token": "some_token", - "session": {} + "session": {}, } mock_request.return_value = mock_response - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) auth_client.id = "some_id" auth_client.get_login_token() self.assertEqual(auth_client.token.session_token, "some_token") self.assertIsNone(auth_client.token.expires_at) - def test_check_if_token_has_expired_valid(self): - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) - future_time = datetime.now(timezone.utc) + timedelta(hours=1) - auth_client.token = Token(session_token=auth_session_id, expires_at=future_time) - self.assertFalse(auth_client.check_if_token_has_expired()) - - @patch('pyprediktormapclient.auth_client.request_from_api') + @patch("pyprediktormapclient.auth_client.request_from_api") def test_get_login_token_invalid_expiry_format(self, mock_request): mock_response = { "Success": True, "session_token": "some_token", - "session": { - "expires_at": "invalid_date_format" - } + "session": {"expires_at": "invalid_date_format"}, } mock_request.return_value = mock_response - auth_client = AUTH_CLIENT(rest_url=URL, username=username, password=password) + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) auth_client.id = "some_id" auth_client.get_login_token() self.assertEqual(auth_client.token.session_token, "some_token") @@ -577,9 +535,11 @@ def test_get_self_service_token_expired_none(self): token_expired = auth_client.check_if_token_has_expired() assert token_expired - @patch.object(AUTH_CLIENT, 'get_login_id') - @patch.object(AUTH_CLIENT, 'get_login_token') - def test_request_new_ory_token(self, mock_get_login_token, mock_get_login_id): + @patch.object(AUTH_CLIENT, "get_login_id") + @patch.object(AUTH_CLIENT, "get_login_token") + def test_request_new_ory_token( + self, mock_get_login_token, mock_get_login_id + ): auth_client = AUTH_CLIENT( rest_url=URL, username=username, password=password ) @@ -587,22 +547,43 @@ def test_request_new_ory_token(self, mock_get_login_token, mock_get_login_id): mock_get_login_id.assert_called_once() mock_get_login_token.assert_called_once() - def test_token_remove_nanoseconds(self): + def test_remove_nanoseconds_validator(self): + self.assertIsNone(Token.remove_nanoseconds(None)) - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password + dt = datetime.now() + self.assertEqual(Token.remove_nanoseconds(dt), dt) + + expected_dt = datetime( + 2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc + ) + self.assertEqual( + Token.remove_nanoseconds(auth_expires_at), expected_dt ) + invalid_str = "2023-05-01 12:34:56" + self.assertEqual(Token.remove_nanoseconds(invalid_str), invalid_str) - auth_expires_at_datetime = datetime(2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc) + def test_token_expires_at_handling(self): - auth_client.token = Token(session_token=auth_session_id, expires_at=auth_expires_at) + auth_client = AUTH_CLIENT( + rest_url=URL, username=username, password=password + ) + auth_expires_at_datetime = datetime( + 2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc + ) + auth_client.token = Token( + session_token=auth_session_id, expires_at=auth_expires_at + ) assert auth_client.token.expires_at == auth_expires_at_datetime - auth_client.token = Token(session_token=auth_session_id, expires_at=None) + auth_client.token = Token( + session_token=auth_session_id, expires_at=None + ) assert auth_client.token.expires_at is None invalid_datetime = datetime(2024, 8, 29, 0, 0, tzinfo=timezone.utc) - auth_client.token = Token(session_token=auth_session_id, expires_at=invalid_datetime) + auth_client.token = Token( + session_token=auth_session_id, expires_at=invalid_datetime + ) assert auth_client.token.expires_at == invalid_datetime diff --git a/tests/shared_test.py b/tests/shared_test.py index 781b451..b9ed6c1 100644 --- a/tests/shared_test.py +++ b/tests/shared_test.py @@ -31,9 +31,12 @@ def raise_for_status(self): if self.status_code >= 400: raise RequestException(f"HTTP Error: {self.status_code}") + def mocked_requests(*args, **kwargs): if args[0] == f"{URL}something": - return MockResponse(return_json, 200, {"Content-Type": "application/json"}) + return MockResponse( + return_json, 200, {"Content-Type": "application/json"} + ) return MockResponse(None, 404) @@ -67,61 +70,75 @@ def test_request_from_api_method_post(self, mock_get): @mock.patch("requests.get") def test_request_from_api_json_response(self, mock_get): json_response = {"key": "value"} - mock_get.return_value = MockResponse(json_response, 200, headers={"Content-Type": "application/json"}) - - result = request_from_api( - rest_url=URL, - method="GET", - endpoint="test" + mock_get.return_value = MockResponse( + json_response, 200, headers={"Content-Type": "application/json"} ) - + + result = request_from_api(rest_url=URL, method="GET", endpoint="test") + self.assertEqual(result, json_response) @mock.patch("requests.get") def test_request_from_api_non_json_response(self, mock_get): non_json_response = "This is a plain text response" - mock_get.return_value = MockResponse(non_json_response, 200, headers={"Content-Type": "text/plain"}) - - result = request_from_api( - rest_url=URL, - method="GET", - endpoint="test" + mock_get.return_value = MockResponse( + non_json_response, 200, headers={"Content-Type": "text/plain"} + ) + + result = request_from_api(rest_url=URL, method="GET", endpoint="test") + + self.assertEqual( + result, + {"error": "Non-JSON response", "content": non_json_response}, + ) + mock_get.assert_called_once_with( + f"{URL}test", timeout=(3, 27), params=None, headers=None ) - - self.assertEqual(result, {"error": "Non-JSON response", "content": non_json_response}) - mock_get.assert_called_once_with(f"{URL}test", timeout=(3, 27), params=None, headers=None) @mock.patch("requests.get") def test_request_from_api_exception(self, mock_get): mock_get.side_effect = RequestException("Network error") with self.assertRaises(RequestException): - request_from_api( - rest_url=URL, - method="GET", - endpoint="test" - ) + request_from_api(rest_url=URL, method="GET", endpoint="test") @mock.patch("requests.get") def test_request_from_api_extended_timeout(self, mock_get): - mock_get.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) - result = request_from_api(rest_url=URL, method="GET", endpoint="something", extended_timeout=True) - mock_get.assert_called_with(f"{URL}something", timeout=(3, 300), params=None, headers=None) + mock_get.return_value = MockResponse( + return_json, 200, {"Content-Type": "application/json"} + ) + result = request_from_api( + rest_url=URL, + method="GET", + endpoint="something", + extended_timeout=True, + ) + mock_get.assert_called_with( + f"{URL}something", timeout=(3, 300), params=None, headers=None + ) assert result == return_json @mock.patch("requests.Session") def test_request_from_api_with_session(self, mock_session): mock_session_instance = mock_session.return_value - mock_session_instance.get.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) + mock_session_instance.get.return_value = MockResponse( + return_json, 200, {"Content-Type": "application/json"} + ) session = requests.Session() - result = request_from_api(rest_url=URL, method="GET", endpoint="something", session=session) + result = request_from_api( + rest_url=URL, method="GET", endpoint="something", session=session + ) - mock_session_instance.get.assert_called_with(f"{URL}something", timeout=(3, 27), params=None, headers=None) + mock_session_instance.get.assert_called_with( + f"{URL}something", timeout=(3, 27), params=None, headers=None + ) assert result == return_json @mock.patch("requests.post") def test_request_from_api_with_params_and_headers(self, mock_post): - mock_post.return_value = MockResponse(return_json, 200, {"Content-Type": "application/json"}) + mock_post.return_value = MockResponse( + return_json, 200, {"Content-Type": "application/json"} + ) params = {"param": "value"} headers = {"Authorization": "Bearer token"} @@ -131,14 +148,14 @@ def test_request_from_api_with_params_and_headers(self, mock_post): endpoint="something", data="test_data", params=params, - headers=headers + headers=headers, ) mock_post.assert_called_with( f"{URL}something", data="test_data", headers=headers, timeout=(3, 27), - params=params + params=params, ) assert result == return_json From d43bd9a6d71f3f29b36cafe83e6cee7c18c9ea60 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 30 Aug 2024 11:23:25 +0200 Subject: [PATCH 08/35] Fixed test failing in CI. --- tests/auth_client_test.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 7757ffb..3d64c53 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -464,11 +464,12 @@ def test_get_self_service_login_token_successful(self, mock_get): ) auth_client.id = auth_id auth_client.get_login_token() - test_token = Token( - session_token=auth_session_id, expires_at=auth_expires_at - ) - assert auth_client.token.session_token == test_token.session_token - assert auth_client.token.expires_at == test_token.expires_at + + self.assertIsNotNone(auth_client.token) + self.assertEqual(auth_client.token.session_token, auth_session_id) + + expected_expires_at = Token.remove_nanoseconds(auth_expires_at) + self.assertEqual(auth_client.token.expires_at, expected_expires_at) @mock.patch( "requests.post", From d3da58fbb73cf0ae9afe63a3522802a92d3c9bbe Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 30 Aug 2024 14:05:07 +0200 Subject: [PATCH 09/35] Achieved 100% code coverage for ModelIndex class. --- src/pyprediktormapclient/model_index.py | 16 +-- tests/model_index_test.py | 148 +++++++++++++++++++----- 2 files changed, 121 insertions(+), 43 deletions(-) diff --git a/src/pyprediktormapclient/model_index.py b/src/pyprediktormapclient/model_index.py index 5fdcb43..10d4af4 100644 --- a/src/pyprediktormapclient/model_index.py +++ b/src/pyprediktormapclient/model_index.py @@ -3,7 +3,7 @@ from typing import List import requests from datetime import date, datetime -from pydantic import AnyUrl, ValidationError +from pydantic import AnyUrl from pydantic_core import Url from pyprediktormapclient.shared import request_from_api @@ -82,7 +82,6 @@ def get_namespace_array(self) -> str: headers=self.headers, session=self.session, ) - return content def get_object_types(self) -> str: @@ -93,7 +92,6 @@ def get_object_types(self) -> str: headers=self.headers, session=self.session, ) - return content def get_object_type_id_from_name(self, type_name: str) -> str: @@ -113,10 +111,7 @@ def get_object_type_id_from_name(self, type_name: str) -> str: ) except StopIteration: obj_type = {} - object_type_id = obj_type.get( - "Id" - ) # Returns None if the ID is not present - + object_type_id = obj_type.get("Id") return object_type_id def get_objects_of_type(self, type_name: str) -> str: @@ -141,7 +136,6 @@ def get_objects_of_type(self, type_name: str) -> str: headers=self.headers, session=self.session, ) - return content def get_object_descendants( @@ -161,7 +155,7 @@ def get_object_descendants( A json-formatted string with descendats data of selected object (or None if the type is not found) """ if type_name is None or not ids: - raise ValidationError("type_name and ids cannot be None or empty") + raise ValueError("type_name and ids cannot be None or empty") id = self.get_object_type_id_from_name(type_name) body = json.dumps( @@ -179,7 +173,6 @@ def get_object_descendants( headers=self.headers, session=self.session, ) - return content def get_object_ancestors( @@ -199,7 +192,7 @@ def get_object_ancestors( A json-formatted string with ancestors data of selected object (or None if the type is not found) """ if type_name is None or not ids: - raise ValidationError("type_name and ids cannot be None or empty") + raise ValueError("type_name and ids cannot be None or empty") id = self.get_object_type_id_from_name(type_name) body = json.dumps( @@ -217,5 +210,4 @@ def get_object_ancestors( headers=self.headers, session=self.session, ) - return content diff --git a/tests/model_index_test.py b/tests/model_index_test.py index d940f6e..9b2f5c4 100644 --- a/tests/model_index_test.py +++ b/tests/model_index_test.py @@ -3,6 +3,9 @@ from unittest import mock from pydantic import ValidationError, BaseModel, AnyUrl from pyprediktormapclient.model_index import ModelIndex +from datetime import datetime, date +from pydantic_core import Url +import requests URL = "http://someserver.somedomain.com/v1/" object_types = [ @@ -64,12 +67,15 @@ class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code - self.raise_for_status = mock.Mock(return_value=False) self.headers = {"Content-Type": "application/json"} def json(self): return self.json_data + def raise_for_status(self): + if self.status_code >= 400: + raise requests.HTTPError(f"{self.status_code} Client Error") + if args[0] == f"{URL}query/object-types": return MockResponse(object_types, 200) elif args[0] == f"{URL}query/namespace-array": @@ -88,17 +94,96 @@ class AnyUrlModel(BaseModel): url: AnyUrl -# Our test case class -class ModelIndexTestCase(unittest.TestCase): +class TestCaseModelIndex(unittest.TestCase): + + @mock.patch("requests.get", side_effect=mocked_requests) + @mock.patch("pyprediktormapclient.model_index.ModelIndex.get_object_types") + def test_init_variations(self, mock_get_object_types, mock_get): + mock_get_object_types.return_value = object_types + + # Without auth_client + model = ModelIndex(url=URL) + assert "Authorization" not in model.headers + assert "Cookie" not in model.headers + + # With auth_client, no token, no session_token + auth_client = mock.Mock(spec=[]) + auth_client.token = None + model = ModelIndex(url=URL, auth_client=auth_client) + assert "Authorization" not in model.headers + assert "Cookie" not in model.headers + + # With auth_client, token, and session_token + auth_client = mock.Mock() + auth_client.token = mock.Mock() + auth_client.token.session_token = "test_token" + auth_client.session_token = "ory_session_token" + model = ModelIndex(url=URL, auth_client=auth_client) + assert model.headers["Authorization"] == "Bearer test_token" + assert ( + model.headers["Cookie"] == "ory_kratos_session=ory_session_token" + ) + + # With auth_client, no token, with session_token + auth_client.token = None + model = ModelIndex(url=URL, auth_client=auth_client) + assert "Authorization" not in model.headers + assert ( + model.headers["Cookie"] == "ory_kratos_session=ory_session_token" + ) + + # With session + session = requests.Session() + model = ModelIndex(url=URL, session=session) + assert model.session == session + def test_malformed_url(self): with pytest.raises(ValidationError): AnyUrlModel(url="not_an_url") @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_types(self, mock_get): + def test_json_serial(self, mock_get): model = ModelIndex(url=URL) - result = model.get_object_types() - assert result == object_types + + dt = datetime(2023, 1, 1, 12, 0, 0) + assert model.json_serial(dt) == "2023-01-01T12:00:00" + + d = date(2023, 1, 1) + assert model.json_serial(d) == "2023-01-01" + + url = Url("http://example.com") + assert model.json_serial(url) == "http://example.com/" + + with pytest.raises(TypeError): + model.json_serial(set()) + + @mock.patch("requests.get", side_effect=mocked_requests) + def test_check_auth_client(self, mock_get): + model = ModelIndex(url=URL) + model.auth_client = mock.Mock() + + model.auth_client.token = mock.Mock() + model.auth_client.token.session_token = "new_token" + content = {"error": {"code": 404}} + model.check_auth_client(content) + model.auth_client.request_new_ory_token.assert_called_once() + assert model.headers["Authorization"] == "Bearer new_token" + + model.auth_client.token = None + model.auth_client.request_new_ory_token = mock.Mock() + + def side_effect(): + model.auth_client.token = mock.Mock() + model.auth_client.token.session_token = "new_token" + + model.auth_client.request_new_ory_token.side_effect = side_effect + model.check_auth_client(content) + model.auth_client.request_new_ory_token.assert_called_once() + assert model.headers["Authorization"] == "Bearer new_token" + + content = {"ErrorMessage": "Some error"} + with pytest.raises(RuntimeError, match="Some error"): + model.check_auth_client(content) @mock.patch("requests.get", side_effect=mocked_requests) def test_get_namespace_array(self, mock_get): @@ -107,18 +192,32 @@ def test_get_namespace_array(self, mock_get): assert result == namespaces @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_of_type(self, mock_get): + def test_get_object_types(self, mock_get): model = ModelIndex(url=URL) - with mock.patch("requests.post", side_effect=mocked_requests): - result = model.get_objects_of_type(type_name="IPVBaseCalculate") - assert result == objects_of_type + result = model.get_object_types() + assert result == object_types + + @mock.patch("requests.get", side_effect=mocked_requests) + def test_get_object_type_id_from_name(self, mock_get): + model = ModelIndex(url=URL) + assert ( + model.get_object_type_id_from_name("IPVBaseCalculate") + == "6:0:1029" + ) + assert model.get_object_type_id_from_name("NonExistentType") is None @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_of_type_with_wrong_type(self, mock_get): + def test_get_objects_of_type(self, mock_get): model = ModelIndex(url=URL) with mock.patch("requests.post", side_effect=mocked_requests): - result = model.get_objects_of_type(type_name="IPVBaseCalculate2") - assert result is None + assert ( + model.get_objects_of_type(type_name="IPVBaseCalculate") + == objects_of_type + ) + assert ( + model.get_objects_of_type(type_name="IPVBaseCalculate2") + is None + ) @mock.patch("requests.get", side_effect=mocked_requests) def test_get_object_descendants(self, mock_get): @@ -131,19 +230,12 @@ def test_get_object_descendants(self, mock_get): ) assert result == descendants - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_descendants_with_no_type_id(self, mock_get): - model = ModelIndex(url=URL) - with pytest.raises(TypeError): - result = model.get_object_descendants( + with self.assertRaises(ValueError): + model.get_object_descendants( type_name=None, ids=["Anything"], domain="PV_Assets" ) - assert result is None - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_descendants_with_no_ids(self, mock_get): - model = ModelIndex(url=URL) - with pytest.raises(TypeError): + with self.assertRaises(ValueError): model.get_object_descendants( type_name="IPVBaseCalculate", ids=None, domain="PV_Assets" ) @@ -159,18 +251,12 @@ def test_get_object_ancestors(self, mock_get): ) assert result == ancestors - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_ancestors_with_no_id(self, mock_get): - model = ModelIndex(url=URL) - with pytest.raises(TypeError): + with self.assertRaises(ValueError): model.get_object_ancestors( type_name=None, ids=["Anything"], domain="PV_Assets" ) - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_ancestors_with_no_ids(self, mock_get): - model = ModelIndex(url=URL) - with pytest.raises(TypeError): + with self.assertRaises(ValueError): model.get_object_ancestors( type_name="IPVBaseCalculate", ids=None, domain="PV_Assets" ) From f445dbfbe1caea99f20a5698e12f54fb86508228 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 30 Aug 2024 15:57:45 +0200 Subject: [PATCH 10/35] Enhanced OPC_UA test suite for improved code coverage. --- tests/opc_ua_test.py | 209 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 202 insertions(+), 7 deletions(-) diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 3ec78c6..e3a94c2 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1,12 +1,20 @@ import unittest from unittest import mock import pytest -import datetime +from datetime import datetime, timedelta import aiohttp import pandas.api.types as ptypes from pydantic import ValidationError, AnyUrl, BaseModel +from requests.exceptions import HTTPError from typing import List from copy import deepcopy +from pydantic_core import Url +import asyncio +import requests +import json +import pandas as pd +from aiohttp import ClientResponseError, ClientError, RequestInfo +from yarl import URL as YarlURL from pyprediktormapclient.opc_ua import OPC_UA from pyprediktormapclient.auth_client import AUTH_CLIENT, Token @@ -477,7 +485,71 @@ class AnyUrlModel(BaseModel): url: AnyUrl -class TestOPCUA(unittest.TestCase): +class TestCaseOPCUA(unittest.TestCase): + + def test_json_serial(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + dt = datetime(2023, 1, 1, 12, 0, 0) + self.assertEqual(opc.json_serial(dt), "2023-01-01T12:00:00") + + url = Url("http://example.com") + self.assertEqual(opc.json_serial(url).rstrip('/'), "http://example.com") + + with self.assertRaises(TypeError): + opc.json_serial(set()) + + def test_check_auth_client(self): + auth_client_mock = mock.Mock() + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) + + content = {"error": {"code": 404}} + opc.check_auth_client(content) + auth_client_mock.request_new_ory_token.assert_called_once() + + content = {"error": {"code": 500}, "ErrorMessage": "Server Error"} + with self.assertRaises(RuntimeError): + opc.check_auth_client(content) + + def test_check_if_ory_session_token_is_valid_refresh(self): + auth_client_mock = mock.Mock() + auth_client_mock.check_if_token_has_expired.return_value = True + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) + + opc.check_if_ory_session_token_is_valid_refresh() + auth_client_mock.check_if_token_has_expired.assert_called_once() + auth_client_mock.refresh_token.assert_called_once() + + @mock.patch("requests.post") + @mock.patch("pyprediktormapclient.shared.request_from_api") + def test_get_values_with_auth_client(self, mock_request_from_api, mock_post): + auth_client_mock = mock.Mock() + auth_client_mock.token.session_token = "test_token" + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) + + mock_response = mock.Mock() + mock_response.content = json.dumps({"error": {"code": 404}}).encode() + mock_request_from_api.side_effect = [ + HTTPError("404 Client Error", response=mock_response), + successful_live_response + ] + mock_post.return_value = MockResponse(successful_live_response, 200) + + result = opc.get_values(list_of_ids) + + self.assertIsNotNone(result) + self.assertTrue(mock_request_from_api.call_count > 0 or mock_post.call_count > 0, + "Neither request_from_api nor post was called") + + self.assertEqual(len(result), len(list_of_ids)) + for i, item in enumerate(result): + self.assertEqual(item['Id'], list_of_ids[i]['Id']) + + if opc.headers: + self.assertIn("Authorization", opc.headers, "Authorization header is missing") + self.assertIn("test_token", opc.headers.get("Authorization", ""), + "Session token not found in Authorization header") + def test_malformed_rest_url(self): with pytest.raises(ValidationError): AnyUrlModel(rest_url="not_an_url", opcua_url=OPC_URL) @@ -684,6 +756,24 @@ def test_get_write_live_values_empty(self, mock_get): with pytest.raises(ValueError): tsdata.write_values(list_of_write_values) + def test_process_content(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + content = { + "Success": True, + "HistoryReadResults": [ + { + "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, + "DataValues": [ + {"Value": {"Type": 11, "Body": 1.23}, "SourceTimestamp": "2023-01-01T00:00:00Z"} + ] + } + ] + } + result = opc._process_content(content) + self.assertIsInstance(result, pd.DataFrame) + self.assertEqual(len(result), 1) + self.assertEqual(result.iloc[0]["Value.Body"], 1.23) + @mock.patch( "requests.post", side_effect=successful_write_historical_mocked_requests, @@ -812,8 +902,8 @@ def unsuccessful_async_mock_response(*args, **kwargs): async def make_historical_request(): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) return await tsdata.get_historical_aggregated_values_asyn( - start_time=(datetime.datetime.now() - datetime.timedelta(30)), - end_time=(datetime.datetime.now() - datetime.timedelta(29)), + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), pro_interval=3600000, agg_name="Average", variable_list=list_of_ids, @@ -823,14 +913,56 @@ async def make_historical_request(): async def make_raw_historical_request(): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) return await tsdata.get_raw_historical_values_asyn( - start_time=(datetime.datetime.now() - datetime.timedelta(30)), - end_time=(datetime.datetime.now() - datetime.timedelta(29)), + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), variable_list=list_of_ids, ) @pytest.mark.asyncio -class TestAsyncOPCUA: +class TestCaseAsyncOPCUA(): + + @mock.patch("aiohttp.ClientSession.post") + async def test_make_request_retries(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + mock_response = mock.Mock() + mock_response.status = 500 + mock_response.json = mock.AsyncMock(return_value={"error": "Server Error"}) + + mock_post.side_effect = [ + ClientResponseError( + request_info=aiohttp.RequestInfo( + url=YarlURL("http://example.com"), + method="POST", + headers={}, + real_url=YarlURL("http://example.com") + ), + history=(), + status=500 + ), + ClientError(), + mock_response + ] + + try: + result = await opc._make_request("test_endpoint", {}, 3, 0) + print(f"Result: {result}") + except Exception as e: + print(f"Exception occurred: {type(e).__name__} - {str(e)}") + print(f"Mock post call count: {mock_post.call_count}") + raise + + assert mock_post.call_count == 3 + + @mock.patch("aiohttp.ClientSession.post") + async def test_make_request_max_retries_reached(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + mock_post.side_effect = ClientError() + + with self.assertRaises(RuntimeError): + await opc._make_request("test_endpoint", {}, 3, 0) + self.assertEqual(mock_post.call_count, 3) @mock.patch("aiohttp.ClientSession.post") async def test_historical_values_success(self, mock_post): @@ -855,6 +987,69 @@ async def test_historical_values_success(self, mock_post): "Double", ] + @mock.patch("pyprediktormapclient.opc_ua.OPC_UA._make_request") + async def test_get_historical_values(self, mock_make_request): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + mock_make_request.return_value = { + "Success": True, + "HistoryReadResults": [ + { + "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, + "DataValues": [ + {"Value": {"Type": 11, "Body": 1.23}, "SourceTimestamp": "2023-01-01T00:00:00Z"} + ] + } + ] + } + + start_time = datetime(2023, 1, 1) + end_time = datetime(2023, 1, 2) + variable_list = ["SOMEID"] + + result = await opc.get_historical_values( + start_time, end_time, variable_list, "test_endpoint", + lambda vars: [{"NodeId": var} for var in vars] + ) + + assert isinstance(result, pd.DataFrame) + assert len(result) == 1 + assert result.iloc[0]["Value.Body"] == 1.23 + + @mock.patch("aiohttp.ClientSession.post") + async def test_get_raw_historical_values_asyn(self, mock_post): + mock_post.return_value = AsyncMockResponse( + json_data=successful_raw_historical_result, status_code=200 + ) + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + result = await opc.get_raw_historical_values_asyn( + start_time=datetime(2023, 1, 1), + end_time=datetime(2023, 1, 2), + variable_list=["SOMEID"], + limit_start_index=0, + limit_num_records=100 + ) + assert isinstance(result, pd.DataFrame) + assert "Value" in result.columns + assert "Timestamp" in result.columns + + @mock.patch("aiohttp.ClientSession.post") + async def test_get_historical_aggregated_values_asyn(self, mock_post): + mock_post.return_value = AsyncMockResponse( + json_data=successful_historical_result, status_code=200 + ) + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + result = await opc.get_historical_aggregated_values_asyn( + start_time=datetime(2023, 1, 1), + end_time=datetime(2023, 1, 2), + pro_interval=3600000, + agg_name="Average", + variable_list=["SOMEID"] + ) + assert isinstance(result, pd.DataFrame) + assert "Value" in result.columns + assert "Timestamp" in result.columns + assert "StatusSymbol" in result.columns + @mock.patch("aiohttp.ClientSession.post") async def test_historical_values_no_dict(self, mock_post): with pytest.raises(RuntimeError): From ae27ec70292c3ab48aa93410f4f5cd1512f70d6b Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 2 Sep 2024 16:24:27 +0200 Subject: [PATCH 11/35] Enhanced OPC_UA class and added comprehensive unit tests. --- src/pyprediktormapclient/opc_ua.py | 19 +- tests/opc_ua_test.py | 338 +++++++++++++++++++++-------- 2 files changed, 255 insertions(+), 102 deletions(-) diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 0d07400..9b49f2f 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -203,6 +203,9 @@ def __init__( self.session = session self.helper = AsyncIONotebookHelper() + if not str(self.opcua_url).startswith("opc.tcp://"): + raise ValueError("Invalid OPC UA URL") + if self.auth_client is not None: if self.auth_client.token is not None: self.headers["Authorization"] = ( @@ -297,15 +300,6 @@ def get_values(self, variable_list: List[Variables]) -> List: self.check_auth_client(json.loads(e.response.content)) else: raise RuntimeError(f"Error message {e}") - finally: - content = request_from_api( - rest_url=self.rest_url, - method="POST", - endpoint="values/get", - data=json.dumps([body], default=self.json_serial), - headers=self.headers, - extended_timeout=True, - ) for var in vars: # Add default None values @@ -361,7 +355,7 @@ def _check_content(self, content: Dict[str, Any]) -> None: if not content.get("Success"): raise RuntimeError(content.get("ErrorMessage")) if "HistoryReadResults" not in content: - raise RuntimeError(content.get("ErrorMessage")) + raise RuntimeError("No history read results returned from the server") def _process_df( self, df_result: pd.DataFrame, columns: Dict[str, str] @@ -501,6 +495,11 @@ async def process_batch(variables, time_batch): ] results = await asyncio.gather(*tasks) + results = [df for df in results if df is not None] + + if not results: + return pd.DataFrame() + combined_df = pd.concat(results, ignore_index=True) return combined_df diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index e3a94c2..9d045e7 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -13,10 +13,12 @@ import requests import json import pandas as pd -from aiohttp import ClientResponseError, ClientError, RequestInfo +from parameterized import parameterized +from aioresponses import aioresponses +from aiohttp.client_exceptions import ClientResponseError, ClientError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA +from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, WriteHistoricalVariables, TYPE_LIST from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -487,11 +489,20 @@ class AnyUrlModel(BaseModel): class TestCaseOPCUA(unittest.TestCase): + def test_rest_url_ends_with_slash(self): + url_with_trailing_slash = URL.rstrip("/") + "/" + opc = OPC_UA(rest_url=url_with_trailing_slash, opcua_url=OPC_URL) + assert opc.rest_url == URL + + def test_invalid_opcua_url(self): + with pytest.raises(ValueError, match="Invalid OPC UA URL"): + OPC_UA(rest_url=URL, opcua_url="http://invalidurl.com") + def test_json_serial(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - dt = datetime(2023, 1, 1, 12, 0, 0) - self.assertEqual(opc.json_serial(dt), "2023-01-01T12:00:00") + dt = datetime(2024, 9, 2, 12, 0, 0) + self.assertEqual(opc.json_serial(dt), "2024-09-02T12:00:00") url = Url("http://example.com") self.assertEqual(opc.json_serial(url).rstrip('/'), "http://example.com") @@ -511,6 +522,10 @@ def test_check_auth_client(self): with self.assertRaises(RuntimeError): opc.check_auth_client(content) + content = {"error": {}} + with pytest.raises(RuntimeError): + opc.check_auth_client(content) + def test_check_if_ory_session_token_is_valid_refresh(self): auth_client_mock = mock.Mock() auth_client_mock.check_if_token_has_expired.return_value = True @@ -574,6 +589,18 @@ def test_get_value_type(self): assert "description" in result assert result["type"] == "Boolean" + def test_variable_list_not_list(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + with pytest.raises(TypeError, match="Unsupported type in variable_list"): + opc.get_values("not_a_list") + + def test_get_value_type_not_found(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + result = opc._get_value_type(100000) + assert result["id"] is None + assert result["type"] is None + assert result["description"] is None + def test_get_variable_list_as_list(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) var = Variables(Id="ID", Namespace=1, IdType=2) @@ -582,70 +609,41 @@ def test_get_variable_list_as_list(self): assert "Id" in result[0] assert result[0]["Id"] == "ID" + def test_get_variable_list_as_list_invalid_type(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + with self.assertRaises(TypeError): + opc._get_variable_list_as_list([1, 2, 3]) + def test_check_auth_client_is_none(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=None) with pytest.raises(Exception): opc.check_auth_client() + @parameterized.expand([ + (None, None, None), # Without auth client + (AUTH_CLIENT, username, password) # With auth client + ]) @mock.patch("requests.post", side_effect=successful_mocked_requests) - def test_get_live_values_successful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.get_values(list_of_ids) - if list_of_ids: - for num, row in enumerate(list_of_ids): - assert result[num]["Id"] == list_of_ids[num]["Id"] - assert ( - result[num]["Timestamp"] - == successful_live_response[0]["Values"][num][ - "ServerTimestamp" - ] - ) - assert ( - result[num]["Value"] - == successful_live_response[0]["Values"][num]["Value"][ - "Body" - ] - ) - assert ( - result[num]["ValueType"] - == tsdata._get_value_type( - successful_live_response[0]["Values"][num]["Value"][ - "Type" - ] - )["type"] - ) - assert ( - result[num]["StatusCode"] - == successful_live_response[0]["Values"][num][ - "StatusCode" - ]["Code"] - ) - assert ( - result[num]["StatusSymbol"] - == successful_live_response[0]["Values"][num][ - "StatusCode" - ]["Symbol"] - ) + def test_get_live_values_successful(self, AuthClientClass, username, password, mock_get): + if AuthClientClass: + auth_client = AuthClientClass( + rest_url=URL, username=username, password=password + ) + auth_client.token = Token( + session_token=auth_session_id, expires_at=auth_expires_at + ) + tsdata = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client + ) + else: + tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - @mock.patch("requests.post", side_effect=successful_mocked_requests) - def test_get_live_values_successful_with_auth(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) - auth_client.token = Token( - session_token=auth_session_id, expires_at=auth_expires_at - ) - tsdata = OPC_UA( - rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client - ) result = tsdata.get_values(list_of_ids) for num, row in enumerate(list_of_ids): assert result[num]["Id"] == list_of_ids[num]["Id"] assert ( result[num]["Timestamp"] - == successful_live_response[0]["Values"][num][ - "ServerTimestamp" - ] + == successful_live_response[0]["Values"][num]["ServerTimestamp"] ) assert ( result[num]["Value"] @@ -657,18 +655,6 @@ def test_get_live_values_successful_with_auth(self, mock_get): successful_live_response[0]["Values"][num]["Value"]["Type"] )["type"] ) - assert ( - result[num]["StatusCode"] - == successful_live_response[0]["Values"][num]["StatusCode"][ - "Code" - ] - ) - assert ( - result[num]["StatusSymbol"] - == successful_live_response[0]["Values"][num]["StatusCode"][ - "Symbol" - ] - ) @mock.patch("requests.post", side_effect=empty_values_mocked_requests) def test_get_live_values_with_missing_value_and_statuscode(self, mock_get): @@ -756,8 +742,15 @@ def test_get_write_live_values_empty(self, mock_get): with pytest.raises(ValueError): tsdata.write_values(list_of_write_values) + @mock.patch("requests.post") + def test_get_values_no_content(self, mock_request): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + result = opc.get_values(list_of_ids) + assert result[0]["Timestamp"] is None + def test_process_content(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + content = { "Success": True, "HistoryReadResults": [ @@ -770,9 +763,83 @@ def test_process_content(self): ] } result = opc._process_content(content) - self.assertIsInstance(result, pd.DataFrame) - self.assertEqual(len(result), 1) - self.assertEqual(result.iloc[0]["Value.Body"], 1.23) + assert isinstance(result, pd.DataFrame) + assert len(result) == 1 + assert result.iloc[0]["Value.Body"] == 1.23 + + with pytest.raises(RuntimeError, match="No content returned from the server"): + opc._process_content(None) + + content = {"Success": False, "ErrorMessage": "Error"} + with pytest.raises(RuntimeError, match="Error"): + opc._process_content(content) + + content = {"Success": True} + with pytest.raises(RuntimeError, match="No history read results returned from the server"): + opc._process_content(content) + + @mock.patch("requests.post") + def test_get_values_unsuccessful(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + error_response = mock.Mock() + error_response.status_code = 500 + error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") + + mock_post.return_value = error_response + with pytest.raises(RuntimeError): + opc.get_values(list_of_ids) + + @parameterized.expand([ + (WriteVariables, list_of_write_values, "write_values"), + (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values") + ]) + @mock.patch("requests.post") + def test_write_no_content(self, VariableClass, list_of_values, write_method, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + no_content_response = mock.Mock() + no_content_response.status_code = 200 + no_content_response.json.return_value = {"Success": True} + no_content_response.headers = {"Content-Type": "application/json"} + + mock_post.return_value = no_content_response + converted_data = [VariableClass(**item) for item in list_of_values] + + with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): + getattr(opc, write_method)(converted_data) + + @parameterized.expand([ + (WriteVariables, list_of_write_values, "write_values", 500), + (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values", 200) + ]) + @mock.patch("requests.post") + def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, status_code, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + error_response = mock.Mock() + error_response.status_code = status_code + error_response.json.return_value = {"Success": False, "ErrorMessage": "Error"} + error_response.headers = {"Content-Type": "application/json"} + + mock_post.return_value = error_response + converted_data = [VariableClass(**item) for item in list_of_values] + + with pytest.raises(RuntimeError, match="Error"): + getattr(opc, write_method)(converted_data) + + @mock.patch("requests.post") + def test_write_values_no_status_codes(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + error_response = mock.Mock() + error_response.status_code = 200 + error_response.json.return_value = {"Success": True} + error_response.headers = {"Content-Type": "application/json"} + + mock_post.return_value = error_response + with pytest.raises(ValueError): + opc.write_values(list_of_write_values) @mock.patch( "requests.post", @@ -866,6 +933,35 @@ def test_get_write_historical_values_successful_with_error_codes( ][0]["StatusCode"]["Symbol"] ) + @mock.patch("requests.post") + def test_write_historical_values_no_history_update_results(self, mock_request): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values + ] + with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): + opc.write_historical_values(converted_data) + + @mock.patch("requests.post") + def test_write_historical_values_wrong_order(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + wrong_order_response = mock.Mock() + wrong_order_response.status_code = 200 + wrong_order_response.json.return_value = {"Success": False, "ErrorMessage": "UpdateValues attribute missing"} + wrong_order_response.headers = {"Content-Type": "application/json"} + + mock_post.return_value = wrong_order_response + converted_data = [ + WriteHistoricalVariables(**item) + for item in list_of_write_historical_values_in_wrong_order + ] + + with pytest.raises(ValueError, match="Time for variables not in correct order."): + opc.write_historical_values(converted_data) + class AsyncMockResponse: def __init__(self, json_data, status_code): @@ -925,13 +1021,35 @@ class TestCaseAsyncOPCUA(): @mock.patch("aiohttp.ClientSession.post") async def test_make_request_retries(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + error_response = mock.Mock() + error_response.status = 500 + error_response.raise_for_status.side_effect = ClientResponseError( + request_info=aiohttp.RequestInfo( + url=YarlURL("http://example.com"), + method="POST", + headers={}, + real_url=YarlURL("http://example.com") + ), + history=(), + status=500 + ) + + mock_post.side_effect = [error_response, error_response, error_response] + + with pytest.raises(RuntimeError): + await opc._make_request("test_endpoint", {}, 3, 0) + assert mock_post.call_count == 3 - mock_response = mock.Mock() - mock_response.status = 500 - mock_response.json = mock.AsyncMock(return_value={"error": "Server Error"}) + @mock.patch("aiohttp.ClientSession.post") + async def test_make_request_500_error(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + error_response = mock.AsyncMock() + error_response.status = 500 - mock_post.side_effect = [ - ClientResponseError( + async def raise_for_status(): + raise ClientResponseError( request_info=aiohttp.RequestInfo( url=YarlURL("http://example.com"), method="POST", @@ -940,29 +1058,47 @@ async def test_make_request_retries(self, mock_post): ), history=(), status=500 - ), - ClientError(), - mock_response - ] - - try: - result = await opc._make_request("test_endpoint", {}, 3, 0) - print(f"Result: {result}") - except Exception as e: - print(f"Exception occurred: {type(e).__name__} - {str(e)}") - print(f"Mock post call count: {mock_post.call_count}") - raise + ) + error_response.raise_for_status.side_effect = raise_for_status + mock_post.return_value.__aenter__.return_value = error_response - assert mock_post.call_count == 3 + with pytest.raises(ClientResponseError): + await error_response.raise_for_status() + await opc._make_request("test_endpoint", {}, 1, 0) @mock.patch("aiohttp.ClientSession.post") async def test_make_request_max_retries_reached(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - mock_post.side_effect = ClientError() - with self.assertRaises(RuntimeError): + error_response = mock.Mock() + error_response.status = 500 + error_response.raise_for_status.side_effect = ClientResponseError( + request_info=aiohttp.RequestInfo( + url=YarlURL("http://example.com"), + method="POST", + headers={}, + real_url=YarlURL("http://example.com") + ), + history=(), + status=500 + ) + + mock_post.side_effect = [error_response, error_response, error_response] + + with pytest.raises(RuntimeError): await opc._make_request("test_endpoint", {}, 3, 0) - self.assertEqual(mock_post.call_count, 3) + assert mock_post.call_count == 3 + + @mock.patch("aiohttp.ClientSession.post") + async def test_make_request_successful(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + mock_response = mock.Mock() + mock_response.status = 200 + mock_response.json = mock.AsyncMock(return_value={"Success": True}) + mock_post.return_value.__aenter__.return_value = mock_response + + result = await opc._make_request("test_endpoint", {}, 3, 0) + assert result == {"Success": True} @mock.patch("aiohttp.ClientSession.post") async def test_historical_values_success(self, mock_post): @@ -1015,6 +1151,24 @@ async def test_get_historical_values(self, mock_make_request): assert len(result) == 1 assert result.iloc[0]["Value.Body"] == 1.23 + @mock.patch("aiohttp.ClientSession.post") + async def test_get_historical_values_no_results(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + mock_post.return_value = AsyncMockResponse( + json_data={"Success": True, "HistoryReadResults": []}, status_code=200 + ) + + result = await opc.get_historical_values( + start_time=datetime(2023, 1, 1), + end_time=datetime(2023, 1, 2), + variable_list=["SOMEID"], + endpoint="values/historical", + prepare_variables=lambda vars: [{"NodeId": var} for var in vars] + ) + + assert result.empty + @mock.patch("aiohttp.ClientSession.post") async def test_get_raw_historical_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( From f4b0ba7d4c4d6c9855094d01ae205f18b64d7de1 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Tue, 3 Sep 2024 15:56:44 +0200 Subject: [PATCH 12/35] Added new test cases in OPCUA class. --- .coveragerc | 2 + src/pyprediktormapclient/opc_ua.py | 30 +++--- tests/opc_ua_test.py | 157 ++++++++++++++++++++++++----- 3 files changed, 146 insertions(+), 43 deletions(-) diff --git a/.coveragerc b/.coveragerc index 61c326b..593f1fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -26,3 +26,5 @@ exclude_lines = # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: + +show_missing = True \ No newline at end of file diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 9b49f2f..cc759b1 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -294,12 +294,8 @@ def get_values(self, variable_list: List[Variables]) -> List: headers=self.headers, extended_timeout=True, ) - except HTTPError as e: - # print(.get('error').get('code')) - if self.auth_client is not None: - self.check_auth_client(json.loads(e.response.content)) - else: - raise RuntimeError(f"Error message {e}") + except Exception as e: + raise RuntimeError(f"Error in get_values: {str(e)}") from e for var in vars: # Add default None values @@ -615,17 +611,19 @@ def write_values(self, variable_list: List[WriteVariables]) -> List: except HTTPError as e: if self.auth_client is not None: self.check_auth_client(json.loads(e.response.content)) + content = request_from_api( + rest_url=self.rest_url, + method="POST", + endpoint="values/set", + data=json.dumps([body], default=self.json_serial), + headers=self.headers, + extended_timeout=True, + ) else: - raise RuntimeError(f"Error message {e}") - finally: - content = request_from_api( - rest_url=self.rest_url, - method="POST", - endpoint="values/set", - data=json.dumps([body], default=self.json_serial), - headers=self.headers, - extended_timeout=True, - ) + raise RuntimeError(f"Error in write_values: {str(e)}") + except Exception as e: + raise RuntimeError(f"Error in write_values: {str(e)}") + # Return if no content from server if not isinstance(content, dict): return None diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 9d045e7..2f29ba3 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1,7 +1,9 @@ import unittest from unittest import mock +from unittest.mock import patch +from requests.exceptions import HTTPError import pytest -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date import aiohttp import pandas.api.types as ptypes from pydantic import ValidationError, AnyUrl, BaseModel @@ -12,13 +14,13 @@ import asyncio import requests import json +import logging import pandas as pd from parameterized import parameterized -from aioresponses import aioresponses from aiohttp.client_exceptions import ClientResponseError, ClientError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, WriteHistoricalVariables, TYPE_LIST +from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, WriteHistoricalVariables, Value from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -499,44 +501,73 @@ def test_invalid_opcua_url(self): OPC_UA(rest_url=URL, opcua_url="http://invalidurl.com") def test_json_serial(self): + logging.basicConfig(level=logging.DEBUG) opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - + dt = datetime(2024, 9, 2, 12, 0, 0) - self.assertEqual(opc.json_serial(dt), "2024-09-02T12:00:00") - + assert opc.json_serial(dt) == "2024-09-02T12:00:00" + + d = date(2024, 9, 2) + assert opc.json_serial(d) == "2024-09-02" + url = Url("http://example.com") - self.assertEqual(opc.json_serial(url).rstrip('/'), "http://example.com") - - with self.assertRaises(TypeError): + result = opc.json_serial(url) + assert result == "http://example.com/" + assert isinstance(result, str) + + with pytest.raises(TypeError) as excinfo: opc.json_serial(set()) + assert "Type not serializable" in str(excinfo.value) def test_check_auth_client(self): auth_client_mock = mock.Mock() + auth_client_mock.token.session_token = "test_token" opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - + content = {"error": {"code": 404}} opc.check_auth_client(content) auth_client_mock.request_new_ory_token.assert_called_once() + assert opc.headers["Authorization"] == "Bearer test_token" + auth_client_mock.reset_mock() + content = {"error": {"code": 500}, "ErrorMessage": "Server Error"} - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError) as excinfo: opc.check_auth_client(content) + assert str(excinfo.value) == "Server Error" + auth_client_mock.request_new_ory_token.assert_not_called() content = {"error": {}} with pytest.raises(RuntimeError): opc.check_auth_client(content) + auth_client_mock.request_new_ory_token.assert_not_called() def test_check_if_ory_session_token_is_valid_refresh(self): auth_client_mock = mock.Mock() - auth_client_mock.check_if_token_has_expired.return_value = True opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - + + auth_client_mock.check_if_token_has_expired.return_value = True opc.check_if_ory_session_token_is_valid_refresh() auth_client_mock.check_if_token_has_expired.assert_called_once() auth_client_mock.refresh_token.assert_called_once() + + auth_client_mock.reset_mock() + + auth_client_mock.check_if_token_has_expired.return_value = False + opc.check_if_ory_session_token_is_valid_refresh() + auth_client_mock.check_if_token_has_expired.assert_called_once() + auth_client_mock.refresh_token.assert_not_called() - @mock.patch("requests.post") - @mock.patch("pyprediktormapclient.shared.request_from_api") + @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + def test_get_values_request_error(self, mock_request): + mock_request.side_effect = Exception("Test exception") + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + with self.assertRaises(RuntimeError) as context: + opc.get_values(list_of_ids) + self.assertEqual(str(context.exception), "Error in get_values: Test exception") + + @patch('requests.post') + @patch('pyprediktormapclient.shared.request_from_api') def test_get_values_with_auth_client(self, mock_request_from_api, mock_post): auth_client_mock = mock.Mock() auth_client_mock.token.session_token = "test_token" @@ -545,7 +576,7 @@ def test_get_values_with_auth_client(self, mock_request_from_api, mock_post): mock_response = mock.Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() mock_request_from_api.side_effect = [ - HTTPError("404 Client Error", response=mock_response), + requests.exceptions.HTTPError("404 Client Error", response=mock_response), successful_live_response ] mock_post.return_value = MockResponse(successful_live_response, 200) @@ -589,11 +620,6 @@ def test_get_value_type(self): assert "description" in result assert result["type"] == "Boolean" - def test_variable_list_not_list(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - with pytest.raises(TypeError, match="Unsupported type in variable_list"): - opc.get_values("not_a_list") - def test_get_value_type_not_found(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = opc._get_value_type(100000) @@ -603,11 +629,26 @@ def test_get_value_type_not_found(self): def test_get_variable_list_as_list(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - var = Variables(Id="ID", Namespace=1, IdType=2) - list = [var] - result = opc._get_variable_list_as_list(list) - assert "Id" in result[0] - assert result[0]["Id"] == "ID" + + pydantic_var = Variables(Id="SOMEID", Namespace=1, IdType=2) + result = opc._get_variable_list_as_list([pydantic_var]) + assert isinstance(result[0], dict) + assert result[0] == {"Id": "SOMEID", "Namespace": 1, "IdType": 2} + + dict_var = {"Id": "SOMEID2", "Namespace": 2, "IdType": 1} + result = opc._get_variable_list_as_list([dict_var]) + assert isinstance(result[0], dict) + assert result[0] == dict_var + + with pytest.raises(TypeError, match="Unsupported type in variable_list"): + opc._get_variable_list_as_list([123]) + + def test_get_values_variable_list_not_list(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + not_a_list = "not_a_list" + + with pytest.raises(TypeError, match="Unsupported type in variable_list"): + opc.get_values(not_a_list) def test_get_variable_list_as_list_invalid_type(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -696,6 +737,59 @@ def test_get_live_values_no_status_code(self, mock_get): result = tsdata.get_values(list_of_ids) assert result[0]["StatusCode"] is None + @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + def test_get_values_error_handling(self, mock_request): + mock_request.side_effect = Exception("Test exception") + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + with self.assertRaises(RuntimeError) as context: + opc.get_values(list_of_ids) + self.assertEqual(str(context.exception), "Error in get_values: Test exception") + + @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_values_error_handling(self, mock_request): + mock_request.side_effect = Exception("Test exception") + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + with self.assertRaises(RuntimeError) as context: + opc.write_values(list_of_write_values) + self.assertEqual(str(context.exception), "Error in write_values: Test exception") + + @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_values_http_error_handling(self, mock_request): + auth_client_mock = mock.Mock() + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) + + mock_response = mock.Mock() + mock_response.content = json.dumps({"error": {"code": 404}}).encode() + http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) + mock_request.side_effect = [http_error, {"Success": True, "StatusCodes": [{"Code": 0}]}] + + opc.check_auth_client = mock.Mock() + + result = opc.write_values(list_of_write_values) + + opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) + self.assertIsNotNone(result) + self.assertEqual(len(result), len(list_of_write_values)) + + @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_historical_values_http_error_handling(self, mock_request): + auth_client_mock = mock.Mock() + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) + + mock_response = mock.Mock() + mock_response.content = json.dumps({"error": {"code": 404}}).encode() + http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) + mock_request.side_effect = [http_error, {"Success": True, "HistoryUpdateResults": [{}]}] + + opc.check_auth_client = mock.Mock() + + converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + result = opc.write_historical_values(converted_data) + + opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) + self.assertIsNotNone(result) + self.assertEqual(len(result), len(converted_data)) + @mock.patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -1041,6 +1135,15 @@ async def test_make_request_retries(self, mock_post): await opc._make_request("test_endpoint", {}, 3, 0) assert mock_post.call_count == 3 + @mock.patch('aiohttp.ClientSession.post') + async def test_make_request_client_error(self, mock_post): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + + mock_post.side_effect = aiohttp.ClientError("Test client error") + + with pytest.raises(RuntimeError): + await opc._make_request("test_endpoint", {}, 1, 0) + @mock.patch("aiohttp.ClientSession.post") async def test_make_request_500_error(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -1066,7 +1169,7 @@ async def raise_for_status(): await error_response.raise_for_status() await opc._make_request("test_endpoint", {}, 1, 0) - @mock.patch("aiohttp.ClientSession.post") + @mock.patch('aiohttp.ClientSession.post') async def test_make_request_max_retries_reached(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) From 9a0f3a262fdf3a32a7b01ed489db6753f2fe0fbd Mon Sep 17 00:00:00 2001 From: MeenBna Date: Thu, 5 Sep 2024 16:26:51 +0200 Subject: [PATCH 13/35] Refactored OPC UA tests for improved clarity and reduced redundancy. --- pyproject.toml | 1 + src/pyprediktormapclient/opc_ua.py | 13 + tests/opc_ua_test.py | 484 +++++++++++++++-------------- 3 files changed, 261 insertions(+), 237 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2a7edc9..d0f8165 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,4 @@ version_scheme = "no-guess-dev" pythonpath = [ "src" ] +asyncio_default_fixture_loop_scope = "function" \ No newline at end of file diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index cc759b1..393b374 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -294,6 +294,19 @@ def get_values(self, variable_list: List[Variables]) -> List: headers=self.headers, extended_timeout=True, ) + except HTTPError as e: + if self.auth_client is not None: + self.check_auth_client(json.loads(e.response.content)) + content = request_from_api( + rest_url=self.rest_url, + method="POST", + endpoint="values/get", + data=json.dumps([body], default=self.json_serial), + headers=self.headers, + extended_timeout=True, + ) + else: + raise RuntimeError(f"Error in get_values: {str(e)}") from e except Exception as e: raise RuntimeError(f"Error in get_values: {str(e)}") from e diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 2f29ba3..975d038 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1,6 +1,5 @@ import unittest -from unittest import mock -from unittest.mock import patch +from unittest.mock import patch, Mock, AsyncMock from requests.exceptions import HTTPError import pytest from datetime import datetime, timedelta, date @@ -20,7 +19,7 @@ from aiohttp.client_exceptions import ClientResponseError, ClientError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, WriteHistoricalVariables, Value +from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, Value, SubValue, StatsCode from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -316,7 +315,7 @@ class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code - self.raise_for_status = mock.Mock(return_value=False) + self.raise_for_status = Mock(return_value=False) self.headers = {"Content-Type": "application/json"} def json(self): @@ -490,188 +489,148 @@ class AnyUrlModel(BaseModel): class TestCaseOPCUA(unittest.TestCase): + def setUp(self): + self.opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + self.auth_client_mock = Mock() + self.opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + + def test_malformed_rest_url(self): + with pytest.raises(ValidationError): + AnyUrlModel(rest_url="not_an_url", opcua_url=OPC_URL) def test_rest_url_ends_with_slash(self): - url_with_trailing_slash = URL.rstrip("/") + "/" - opc = OPC_UA(rest_url=url_with_trailing_slash, opcua_url=OPC_URL) - assert opc.rest_url == URL + url_with_trailing_slash = self.opc.rest_url.rstrip("/") + "/" + self.opc.rest_url = url_with_trailing_slash + assert self.opc.rest_url == URL + + def test_malformed_opcua_url(self): + with pytest.raises(ValidationError): + AnyUrlModel(rest_url=URL, opcua_url="not_an_url") def test_invalid_opcua_url(self): with pytest.raises(ValueError, match="Invalid OPC UA URL"): OPC_UA(rest_url=URL, opcua_url="http://invalidurl.com") - def test_json_serial(self): - logging.basicConfig(level=logging.DEBUG) - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - - dt = datetime(2024, 9, 2, 12, 0, 0) - assert opc.json_serial(dt) == "2024-09-02T12:00:00" - - d = date(2024, 9, 2) - assert opc.json_serial(d) == "2024-09-02" - + def test_namespaces(self): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"]) + assert "ClientNamespaces" in opc.body + assert "ClientNamespaces" not in self.opc.body + + def test_json_serial_with_url(self): url = Url("http://example.com") - result = opc.json_serial(url) - assert result == "http://example.com/" - assert isinstance(result, str) - - with pytest.raises(TypeError) as excinfo: - opc.json_serial(set()) - assert "Type not serializable" in str(excinfo.value) + result = self.opc.json_serial(url) + self.assertEqual(result, "http://example.com/") + self.assertIsInstance(result, str) - def test_check_auth_client(self): - auth_client_mock = mock.Mock() - auth_client_mock.token.session_token = "test_token" - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - + def test_json_serial_with_datetime(self): + dt = datetime(2024, 9, 2, 12, 0, 0) + result = self.opc.json_serial(dt) + self.assertEqual(result, "2024-09-02T12:00:00") + + def test_json_serial_with_date(self): + d = date(2024, 9, 2) + result = self.opc.json_serial(d) + self.assertEqual(result, "2024-09-02") + + def test_json_serial_with_unsupported_type(self): + unsupported_type = set() + with self.assertRaises(TypeError) as context: + self.opc.json_serial(unsupported_type) + + expected_error_message = f"Type {type(unsupported_type)} not serializable" + self.assertEqual(str(context.exception), expected_error_message) + + def test_check_auth_client_404_error(self): + self.auth_client_mock.token.session_token = "test_token" content = {"error": {"code": 404}} - opc.check_auth_client(content) - auth_client_mock.request_new_ory_token.assert_called_once() - assert opc.headers["Authorization"] == "Bearer test_token" - - auth_client_mock.reset_mock() + self.opc_auth.check_auth_client(content) + self.auth_client_mock.request_new_ory_token.assert_called_once() + assert self.opc_auth.headers["Authorization"] == "Bearer test_token" + def test_check_auth_client_500_error(self): + self.auth_client_mock.token.session_token = "test_token" content = {"error": {"code": 500}, "ErrorMessage": "Server Error"} + with pytest.raises(RuntimeError) as excinfo: - opc.check_auth_client(content) + self.opc_auth.check_auth_client(content) assert str(excinfo.value) == "Server Error" - auth_client_mock.request_new_ory_token.assert_not_called() + self.auth_client_mock.request_new_ory_token.assert_not_called() + def test_check_auth_client_empty_error(self): + self.auth_client_mock.token.session_token = "test_token" content = {"error": {}} - with pytest.raises(RuntimeError): - opc.check_auth_client(content) - auth_client_mock.request_new_ory_token.assert_not_called() - - def test_check_if_ory_session_token_is_valid_refresh(self): - auth_client_mock = mock.Mock() - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - - auth_client_mock.check_if_token_has_expired.return_value = True - opc.check_if_ory_session_token_is_valid_refresh() - auth_client_mock.check_if_token_has_expired.assert_called_once() - auth_client_mock.refresh_token.assert_called_once() - - auth_client_mock.reset_mock() - - auth_client_mock.check_if_token_has_expired.return_value = False - opc.check_if_ory_session_token_is_valid_refresh() - auth_client_mock.check_if_token_has_expired.assert_called_once() - auth_client_mock.refresh_token.assert_not_called() - @mock.patch('pyprediktormapclient.opc_ua.request_from_api') - def test_get_values_request_error(self, mock_request): - mock_request.side_effect = Exception("Test exception") - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - with self.assertRaises(RuntimeError) as context: - opc.get_values(list_of_ids) - self.assertEqual(str(context.exception), "Error in get_values: Test exception") - - @patch('requests.post') - @patch('pyprediktormapclient.shared.request_from_api') - def test_get_values_with_auth_client(self, mock_request_from_api, mock_post): - auth_client_mock = mock.Mock() - auth_client_mock.token.session_token = "test_token" - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - - mock_response = mock.Mock() - mock_response.content = json.dumps({"error": {"code": 404}}).encode() - mock_request_from_api.side_effect = [ - requests.exceptions.HTTPError("404 Client Error", response=mock_response), - successful_live_response - ] - mock_post.return_value = MockResponse(successful_live_response, 200) - - result = opc.get_values(list_of_ids) - - self.assertIsNotNone(result) - self.assertTrue(mock_request_from_api.call_count > 0 or mock_post.call_count > 0, - "Neither request_from_api nor post was called") - - self.assertEqual(len(result), len(list_of_ids)) - for i, item in enumerate(result): - self.assertEqual(item['Id'], list_of_ids[i]['Id']) - - if opc.headers: - self.assertIn("Authorization", opc.headers, "Authorization header is missing") - self.assertIn("test_token", opc.headers.get("Authorization", ""), - "Session token not found in Authorization header") + with pytest.raises(RuntimeError): + self.opc_auth.check_auth_client(content) + self.auth_client_mock.request_new_ory_token.assert_not_called() - def test_malformed_rest_url(self): - with pytest.raises(ValidationError): - AnyUrlModel(rest_url="not_an_url", opcua_url=OPC_URL) + def test_check_auth_client_is_none(self): + with pytest.raises(Exception): + self.opc_auth.check_auth_client() - def test_malformed_opcua_url(self): - with pytest.raises(ValidationError): - AnyUrlModel(rest_url=URL, opcua_url="not_an_url") + def test_check_if_ory_session_token_is_valid_refresh_when_expired(self): + self.auth_client_mock.check_if_token_has_expired.return_value = True + self.opc_auth.check_if_ory_session_token_is_valid_refresh() + self.auth_client_mock.check_if_token_has_expired.assert_called_once() + self.auth_client_mock.refresh_token.assert_called_once() - def test_namespaces(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"]) - assert "ClientNamespaces" in opc.body - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - assert "ClientNamespaces" not in opc.body + def test_check_if_ory_session_token_is_valid_refresh_when_not_expired(self): + self.auth_client_mock.check_if_token_has_expired.return_value = False + self.opc_auth.check_if_ory_session_token_is_valid_refresh() + self.auth_client_mock.check_if_token_has_expired.assert_called_once() + self.auth_client_mock.refresh_token.assert_not_called() def test_get_value_type(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result_none = opc._get_value_type(100000) + result_none = self.opc._get_value_type(100000) assert result_none["id"] is None - result = opc._get_value_type(1) + result = self.opc._get_value_type(1) assert "id" in result assert "type" in result assert "description" in result assert result["type"] == "Boolean" def test_get_value_type_not_found(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = opc._get_value_type(100000) + result = self.opc._get_value_type(100000) assert result["id"] is None assert result["type"] is None assert result["description"] is None def test_get_variable_list_as_list(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - pydantic_var = Variables(Id="SOMEID", Namespace=1, IdType=2) - result = opc._get_variable_list_as_list([pydantic_var]) + result = self.opc._get_variable_list_as_list([pydantic_var]) assert isinstance(result[0], dict) assert result[0] == {"Id": "SOMEID", "Namespace": 1, "IdType": 2} dict_var = {"Id": "SOMEID2", "Namespace": 2, "IdType": 1} - result = opc._get_variable_list_as_list([dict_var]) + result = self.opc._get_variable_list_as_list([dict_var]) assert isinstance(result[0], dict) assert result[0] == dict_var with pytest.raises(TypeError, match="Unsupported type in variable_list"): - opc._get_variable_list_as_list([123]) + self.opc._get_variable_list_as_list([123]) def test_get_values_variable_list_not_list(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) not_a_list = "not_a_list" with pytest.raises(TypeError, match="Unsupported type in variable_list"): - opc.get_values(not_a_list) + self.opc.get_values(not_a_list) def test_get_variable_list_as_list_invalid_type(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with self.assertRaises(TypeError): - opc._get_variable_list_as_list([1, 2, 3]) - - def test_check_auth_client_is_none(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=None) - with pytest.raises(Exception): - opc.check_auth_client() + self.opc._get_variable_list_as_list([1, 2, 3]) @parameterized.expand([ - (None, None, None), # Without auth client - (AUTH_CLIENT, username, password) # With auth client + (None,), # Without auth client + (AUTH_CLIENT,) # With auth client ]) - @mock.patch("requests.post", side_effect=successful_mocked_requests) - def test_get_live_values_successful(self, AuthClientClass, username, password, mock_get): + @patch("requests.post", side_effect=successful_mocked_requests) + def test_get_live_values_successful_response_processing(self, AuthClientClass, mock_get): if AuthClientClass: auth_client = AuthClientClass( - rest_url=URL, username=username, password=password + rest_url=URL, username="test_user", password="test_pass" ) auth_client.token = Token( - session_token=auth_session_id, expires_at=auth_expires_at + session_token="test_session_id", expires_at="2099-01-01T00:00:00Z" ) tsdata = OPC_UA( rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client @@ -680,27 +639,53 @@ def test_get_live_values_successful(self, AuthClientClass, username, password, m tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) + + self.assertEqual(len(result), len(list_of_ids), "Result length should match input length") + for num, row in enumerate(list_of_ids): - assert result[num]["Id"] == list_of_ids[num]["Id"] - assert ( - result[num]["Timestamp"] - == successful_live_response[0]["Values"][num]["ServerTimestamp"] + self.assertEqual(result[num]["Id"], list_of_ids[num]["Id"], "IDs should match") + self.assertEqual( + result[num]["Timestamp"], + successful_live_response[0]["Values"][num]["ServerTimestamp"], + "Timestamps should match" ) - assert ( - result[num]["Value"] - == successful_live_response[0]["Values"][num]["Value"]["Body"] + self.assertEqual( + result[num]["Value"], + successful_live_response[0]["Values"][num]["Value"]["Body"], + "Values should match" ) - assert ( - result[num]["ValueType"] - == tsdata._get_value_type( + self.assertEqual( + result[num]["ValueType"], + tsdata._get_value_type( successful_live_response[0]["Values"][num]["Value"]["Type"] - )["type"] + )["type"], + "Value types should match" ) - @mock.patch("requests.post", side_effect=empty_values_mocked_requests) - def test_get_live_values_with_missing_value_and_statuscode(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.get_values(list_of_ids) + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_get_values_with_auth_client_error_handling(self, mock_request_from_api): + self.auth_client_mock.token.session_token = "test_token" + + mock_response = Mock() + mock_response.content = json.dumps({"error": {"code": 404}}).encode() + mock_request_from_api.side_effect = [ + requests.exceptions.HTTPError("404 Client Error", response=mock_response), + successful_live_response + ] + + result = self.opc_auth.get_values(list_of_ids) + + self.assertIsNotNone(result) + self.assertEqual(mock_request_from_api.call_count, 2, + "request_from_api should be called twice due to retry") + + self.assertIn("Authorization", self.opc_auth.headers, "Authorization header is missing") + self.assertIn("test_token", self.opc_auth.headers.get("Authorization", ""), + "Session token not found in Authorization header") + + @patch("requests.post", side_effect=empty_values_mocked_requests) + def test_get_live_values_with_missing_values(self, mock_get): + result = self.opc.get_values(list_of_ids) for num, row in enumerate(list_of_ids): if num < len(result): assert result[num]["Id"] == list_of_ids[num]["Id"] @@ -708,62 +693,72 @@ def test_get_live_values_with_missing_value_and_statuscode(self, mock_get): result[num]["Timestamp"] == empty_live_response[0]["Values"][num]["ServerTimestamp"] ) - assert result[num]["Value"] is None - assert result[num]["ValueType"] is None - assert result[num]["StatusCode"] is None - assert result[num]["StatusSymbol"] is None - - @mock.patch("requests.post", side_effect=no_mocked_requests) - def test_get_live_values_no_response(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.get_values(list_of_ids) - assert result[0]["Timestamp"] is None - - @mock.patch("requests.post", side_effect=unsuccessful_mocked_requests) - def test_get_live_values_unsuccessful(self, mock_post): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - with pytest.raises(RuntimeError): - tsdata.get_values(list_of_ids) - - @mock.patch("requests.post", side_effect=empty_mocked_requests) - def test_get_live_values_empty(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.get_values(list_of_ids) - assert result[0]["Timestamp"] is None + assert all(result[num][key] is None for key in ["Value", "ValueType"]) - @mock.patch("requests.post", side_effect=no_status_code_mocked_requests) + @patch("requests.post", side_effect=no_status_code_mocked_requests) def test_get_live_values_no_status_code(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.get_values(list_of_ids) + result = self.opc.get_values(list_of_ids) assert result[0]["StatusCode"] is None + assert result[0]["StatusSymbol"] is None + assert all(result[0][key] is not None for key in ["Id", "Timestamp", "Value", "ValueType"]) + + @patch("requests.post", side_effect=no_mocked_requests) + def test_get_live_values_no_response(self, mock_get): + result = self.opc.get_values(list_of_ids) + for item in result: + assert all(item[key] is None for key in ["Timestamp", "Value", "ValueType", "StatusCode", "StatusSymbol"]) + assert "Success" not in result[0] + + @patch("requests.post", side_effect=empty_mocked_requests) + def test_get_live_values_empty_response(self, mock_get): + result = self.opc.get_values(list_of_ids) + + assert len(result) == len(list_of_ids), "Result should have same length as input" + + for item in result: + assert all(item[key] is None for key in ["Timestamp", "Value", "ValueType", "StatusCode", "StatusSymbol"]) + assert all(item[key] is not None for key in ["Id", "Namespace", "IdType"]) - @mock.patch('pyprediktormapclient.opc_ua.request_from_api') - def test_get_values_error_handling(self, mock_request): + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_get_live_values_error_handling(self, mock_request): mock_request.side_effect = Exception("Test exception") - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with self.assertRaises(RuntimeError) as context: - opc.get_values(list_of_ids) + self.opc.get_values(list_of_ids) self.assertEqual(str(context.exception), "Error in get_values: Test exception") - @mock.patch('pyprediktormapclient.opc_ua.request_from_api') - def test_write_values_error_handling(self, mock_request): - mock_request.side_effect = Exception("Test exception") - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - with self.assertRaises(RuntimeError) as context: - opc.write_values(list_of_write_values) - self.assertEqual(str(context.exception), "Error in write_values: Test exception") + @patch("requests.post", side_effect=unsuccessful_mocked_requests) + def test_get_live_values_unsuccessful(self, mock_post): + with pytest.raises(RuntimeError): + self.opc.get_values(list_of_ids) + + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_values_with_non_dict_content(self, mock_request): + mock_request.return_value = "Not a dict" + result = self.opc.write_values([{"NodeId": {"Id": "test", "Namespace": 1, "IdType": 2}, "Value": {"Value": {"Type": 10, "Body": 1.2}, "SourceTimestamp": "2022-11-03T12:00:00Z"}}]) + self.assertIsNone(result) + + def test_write_historical_values_empty_update_values(self): + empty_variable = WriteHistoricalVariables( + NodeId=Variables(Id="test", Namespace=1, IdType=2), + PerformInsertReplace=1, + UpdateValues=[] + ) + with patch('pyprediktormapclient.opc_ua.request_from_api') as mock_request: + mock_request.return_value = {"Success": True, "HistoryUpdateResults": [{}]} + result = self.opc.write_historical_values([empty_variable]) + self.assertIsNotNone(result) - @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + @patch('pyprediktormapclient.opc_ua.request_from_api') def test_write_values_http_error_handling(self, mock_request): - auth_client_mock = mock.Mock() + auth_client_mock = Mock() opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - mock_response = mock.Mock() + mock_response = Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) mock_request.side_effect = [http_error, {"Success": True, "StatusCodes": [{"Code": 0}]}] - opc.check_auth_client = mock.Mock() + opc.check_auth_client = Mock() result = opc.write_values(list_of_write_values) @@ -771,17 +766,17 @@ def test_write_values_http_error_handling(self, mock_request): self.assertIsNotNone(result) self.assertEqual(len(result), len(list_of_write_values)) - @mock.patch('pyprediktormapclient.opc_ua.request_from_api') + @patch('pyprediktormapclient.opc_ua.request_from_api') def test_write_historical_values_http_error_handling(self, mock_request): - auth_client_mock = mock.Mock() + auth_client_mock = Mock() opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - mock_response = mock.Mock() + mock_response = Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) mock_request.side_effect = [http_error, {"Success": True, "HistoryUpdateResults": [{}]}] - opc.check_auth_client = mock.Mock() + opc.check_auth_client = Mock() converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] result = opc.write_historical_values(converted_data) @@ -790,7 +785,27 @@ def test_write_historical_values_http_error_handling(self, mock_request): self.assertIsNotNone(result) self.assertEqual(len(result), len(converted_data)) - @mock.patch("requests.post", side_effect=successful_write_mocked_requests) + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_error_handling_write_values(self, mock_request): + mock_request.side_effect = Exception("Test exception") + with self.assertRaises(RuntimeError) as context: + self.opc.write_values(list_of_write_values) + self.assertEqual(str(context.exception), "Error in write_values: Test exception") + + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_error_handling_write_historical_values(self, mock_request): + mock_request.side_effect = [ + Exception("Test exception"), + Exception("Test exception") + ] + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + input_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] + with self.assertRaises(Exception) as context: + opc.write_historical_values(input_data) + self.assertEqual(str(context.exception), "Test exception") + self.assertEqual(mock_request.call_count, 2) + + @patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.write_values(list_of_write_values) @@ -805,7 +820,7 @@ def test_write_live_values_successful(self, mock_get): ) assert result[num]["WriteSuccess"] is True - @mock.patch( + @patch( "requests.post", side_effect=empty_write_values_mocked_requests ) def test_write_live_values_with_missing_value_and_statuscode( @@ -816,13 +831,13 @@ def test_write_live_values_with_missing_value_and_statuscode( for num, row in enumerate(list_of_write_values): assert result[num]["WriteSuccess"] is False - @mock.patch("requests.post", side_effect=no_write_mocked_requests) + @patch("requests.post", side_effect=no_write_mocked_requests) def test_get_write_live_values_no_response(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.write_values(list_of_write_values) assert result is None - @mock.patch( + @patch( "requests.post", side_effect=unsuccessful_write_mocked_requests ) def test_get_write_live_values_unsuccessful(self, mock_get): @@ -830,18 +845,12 @@ def test_get_write_live_values_unsuccessful(self, mock_get): with pytest.raises(RuntimeError): tsdata.write_values(list_of_write_values) - @mock.patch("requests.post", side_effect=empty_write_mocked_requests) + @patch("requests.post", side_effect=empty_write_mocked_requests) def test_get_write_live_values_empty(self, mock_get): tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with pytest.raises(ValueError): tsdata.write_values(list_of_write_values) - @mock.patch("requests.post") - def test_get_values_no_content(self, mock_request): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = opc.get_values(list_of_ids) - assert result[0]["Timestamp"] is None - def test_process_content(self): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -872,11 +881,11 @@ def test_process_content(self): with pytest.raises(RuntimeError, match="No history read results returned from the server"): opc._process_content(content) - @mock.patch("requests.post") + @patch("requests.post") def test_get_values_unsuccessful(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.Mock() + error_response = Mock() error_response.status_code = 500 error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") @@ -888,11 +897,11 @@ def test_get_values_unsuccessful(self, mock_post): (WriteVariables, list_of_write_values, "write_values"), (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values") ]) - @mock.patch("requests.post") + @patch("requests.post") def test_write_no_content(self, VariableClass, list_of_values, write_method, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - no_content_response = mock.Mock() + no_content_response = Mock() no_content_response.status_code = 200 no_content_response.json.return_value = {"Success": True} no_content_response.headers = {"Content-Type": "application/json"} @@ -907,11 +916,11 @@ def test_write_no_content(self, VariableClass, list_of_values, write_method, moc (WriteVariables, list_of_write_values, "write_values", 500), (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values", 200) ]) - @mock.patch("requests.post") + @patch("requests.post") def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, status_code, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.Mock() + error_response = Mock() error_response.status_code = status_code error_response.json.return_value = {"Success": False, "ErrorMessage": "Error"} error_response.headers = {"Content-Type": "application/json"} @@ -922,11 +931,11 @@ def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, s with pytest.raises(RuntimeError, match="Error"): getattr(opc, write_method)(converted_data) - @mock.patch("requests.post") + @patch("requests.post") def test_write_values_no_status_codes(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.Mock() + error_response = Mock() error_response.status_code = 200 error_response.json.return_value = {"Success": True} error_response.headers = {"Content-Type": "application/json"} @@ -935,7 +944,7 @@ def test_write_values_no_status_codes(self, mock_post): with pytest.raises(ValueError): opc.write_values(list_of_write_values) - @mock.patch( + @patch( "requests.post", side_effect=successful_write_historical_mocked_requests, ) @@ -949,7 +958,8 @@ def test_write_historical_values_successful(self, mock_get): for num, row in enumerate(list_of_write_values): assert result[0]["WriteSuccess"] is True - @mock.patch( + + @patch( "requests.post", side_effect=successful_write_historical_mocked_requests, ) @@ -962,7 +972,7 @@ def test_write_wrong_order_historical_values_successful(self, mock_get): with pytest.raises(ValueError): tsdata.write_historical_values(converted_data) - @mock.patch( + @patch( "requests.post", side_effect=empty_write_historical_mocked_requests ) def test_write_historical_values_with_missing_value_and_statuscode( @@ -976,7 +986,7 @@ def test_write_historical_values_with_missing_value_and_statuscode( with pytest.raises(ValueError): tsdata.write_historical_values(converted_data) - @mock.patch( + @patch( "requests.post", side_effect=no_write_mocked_historical_requests ) def test_get_write_historical_values_no_response(self, mock_get): @@ -988,7 +998,7 @@ def test_get_write_historical_values_no_response(self, mock_get): result = tsdata.write_historical_values(converted_data) assert result is None - @mock.patch( + @patch( "requests.post", side_effect=unsuccessful_write_historical_mocked_requests, ) @@ -1001,7 +1011,7 @@ def test_get_write_historical_values_unsuccessful(self, mock_get): with pytest.raises(RuntimeError): tsdata.write_historical_values(converted_data) - @mock.patch( + @patch( "requests.post", side_effect=successful_write_historical_with_errors_mocked_requests, ) @@ -1027,7 +1037,7 @@ def test_get_write_historical_values_successful_with_error_codes( ][0]["StatusCode"]["Symbol"] ) - @mock.patch("requests.post") + @patch("requests.post") def test_write_historical_values_no_history_update_results(self, mock_request): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -1038,11 +1048,11 @@ def test_write_historical_values_no_history_update_results(self, mock_request): with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): opc.write_historical_values(converted_data) - @mock.patch("requests.post") + @patch("requests.post") def test_write_historical_values_wrong_order(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - wrong_order_response = mock.Mock() + wrong_order_response = Mock() wrong_order_response.status_code = 200 wrong_order_response.json.return_value = {"Success": False, "ErrorMessage": "UpdateValues attribute missing"} wrong_order_response.headers = {"Content-Type": "application/json"} @@ -1112,11 +1122,11 @@ async def make_raw_historical_request(): @pytest.mark.asyncio class TestCaseAsyncOPCUA(): - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_make_request_retries(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.Mock() + error_response = Mock() error_response.status = 500 error_response.raise_for_status.side_effect = ClientResponseError( request_info=aiohttp.RequestInfo( @@ -1135,7 +1145,7 @@ async def test_make_request_retries(self, mock_post): await opc._make_request("test_endpoint", {}, 3, 0) assert mock_post.call_count == 3 - @mock.patch('aiohttp.ClientSession.post') + @patch('aiohttp.ClientSession.post') async def test_make_request_client_error(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -1144,11 +1154,11 @@ async def test_make_request_client_error(self, mock_post): with pytest.raises(RuntimeError): await opc._make_request("test_endpoint", {}, 1, 0) - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_make_request_500_error(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.AsyncMock() + error_response = AsyncMock() error_response.status = 500 async def raise_for_status(): @@ -1169,11 +1179,11 @@ async def raise_for_status(): await error_response.raise_for_status() await opc._make_request("test_endpoint", {}, 1, 0) - @mock.patch('aiohttp.ClientSession.post') + @patch('aiohttp.ClientSession.post') async def test_make_request_max_retries_reached(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = mock.Mock() + error_response = Mock() error_response.status = 500 error_response.raise_for_status.side_effect = ClientResponseError( request_info=aiohttp.RequestInfo( @@ -1192,18 +1202,18 @@ async def test_make_request_max_retries_reached(self, mock_post): await opc._make_request("test_endpoint", {}, 3, 0) assert mock_post.call_count == 3 - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_make_request_successful(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - mock_response = mock.Mock() + mock_response = Mock() mock_response.status = 200 - mock_response.json = mock.AsyncMock(return_value={"Success": True}) + mock_response.json = AsyncMock(return_value={"Success": True}) mock_post.return_value.__aenter__.return_value = mock_response result = await opc._make_request("test_endpoint", {}, 3, 0) assert result == {"Success": True} - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_historical_result, status_code=200 @@ -1226,7 +1236,7 @@ async def test_historical_values_success(self, mock_post): "Double", ] - @mock.patch("pyprediktormapclient.opc_ua.OPC_UA._make_request") + @patch("pyprediktormapclient.opc_ua.OPC_UA._make_request") async def test_get_historical_values(self, mock_make_request): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) mock_make_request.return_value = { @@ -1254,7 +1264,7 @@ async def test_get_historical_values(self, mock_make_request): assert len(result) == 1 assert result.iloc[0]["Value.Body"] == 1.23 - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_get_historical_values_no_results(self, mock_post): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) @@ -1272,7 +1282,7 @@ async def test_get_historical_values_no_results(self, mock_post): assert result.empty - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_get_raw_historical_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_raw_historical_result, status_code=200 @@ -1289,7 +1299,7 @@ async def test_get_raw_historical_values_asyn(self, mock_post): assert "Value" in result.columns assert "Timestamp" in result.columns - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_get_historical_aggregated_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_historical_result, status_code=200 @@ -1307,23 +1317,23 @@ async def test_get_historical_aggregated_values_asyn(self, mock_post): assert "Timestamp" in result.columns assert "StatusSymbol" in result.columns - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_historical_values_no_dict(self, mock_post): with pytest.raises(RuntimeError): await make_historical_request() - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_historical_values_unsuccess(self, mock_post): mock_post.return_value = unsuccessful_async_mock_response() with pytest.raises(RuntimeError): await make_historical_request() - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_historical_values_no_hist(self, mock_post): with pytest.raises(RuntimeError): await make_historical_request() - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_raw_historical_result, status_code=200 @@ -1334,17 +1344,17 @@ async def test_raw_historical_values_success(self, mock_post): ptypes.is_numeric_dtype(result[col]) for col in cols_to_check ) - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_no_dict(self, mock_post): with pytest.raises(RuntimeError): await make_raw_historical_request() - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_unsuccess(self, mock_post): with pytest.raises(RuntimeError): await make_raw_historical_request() - @mock.patch("aiohttp.ClientSession.post") + @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_no_hist(self, mock_post): with pytest.raises(RuntimeError): await make_raw_historical_request() From 6fc8f714c6728665f6a4f3c2fb743de16055e286 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 9 Sep 2024 09:16:31 +0200 Subject: [PATCH 14/35] Enhanced OPC UA tests and improved code coverage. --- src/pyprediktormapclient/opc_ua.py | 6 +- tests/conftest.py | 29 +- tests/opc_ua_test.py | 443 +++++++++++++++-------------- 3 files changed, 257 insertions(+), 221 deletions(-) diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 393b374..4100c59 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -403,14 +403,14 @@ async def _make_request( logging.error( f"HTTP error {response.status}: {error_text}" ) - response.raise_for_status() + await response.raise_for_status() return await response.json() except aiohttp.ClientResponseError as e: - if e.status == 500: + if e.status == 500 or attempt == max_retries - 1: logging.error(f"Server Error: {e}") - raise # For 500 errors, we might want to fail fast + raise # Raise for 500 errors or on last attempt logging.error(f"ClientResponseError: {e}") except aiohttp.ClientError as e: logging.error(f"ClientError in POST request: {e}") diff --git a/tests/conftest.py b/tests/conftest.py index e592c31..039d77f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,24 @@ -"""Dummy conftest.py for pyprediktormapclient. +import pytest +from unittest.mock import AsyncMock +from aiohttp import ClientResponseError +from pyprediktormapclient.opc_ua import OPC_UA -If you don't know what this is for, just leave it empty. -Read more about conftest.py under: -- https://docs.pytest.org/en/stable/fixture.html -- https://docs.pytest.org/en/stable/writing_plugins.html -""" +URL = "http://someserver.somedomain.com/v1/" +OPC_URL = "opc.tcp://nosuchserver.nosuchdomain.com" -# import pytest +@pytest.fixture +def opc(): + opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + return opc + +@pytest.fixture +def mock_error_response(): + response = AsyncMock() + response.text = AsyncMock(return_value="Error Message") + response.raise_for_status.side_effect = ClientResponseError( + request_info=AsyncMock(), + history=(), + status=0, + message="Error Message" + ) + return response \ No newline at end of file diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 975d038..7707078 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -19,7 +19,7 @@ from aiohttp.client_exceptions import ClientResponseError, ClientError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA, Variables, WriteVariables, Value, SubValue, StatsCode +from pyprediktormapclient.opc_ua import OPC_UA, WriteVariables, TYPE_LIST from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -488,11 +488,13 @@ class AnyUrlModel(BaseModel): url: AnyUrl -class TestCaseOPCUA(unittest.TestCase): - def setUp(self): - self.opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) +class TestCaseOPCUA: + @pytest.fixture(autouse=True) + def setup(self, opc): + self.opc = opc self.auth_client_mock = Mock() self.opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + opc.TYPE_DICT = {t["id"]: t["type"] for t in TYPE_LIST} def test_malformed_rest_url(self): with pytest.raises(ValidationError): @@ -511,34 +513,36 @@ def test_invalid_opcua_url(self): with pytest.raises(ValueError, match="Invalid OPC UA URL"): OPC_UA(rest_url=URL, opcua_url="http://invalidurl.com") - def test_namespaces(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"]) - assert "ClientNamespaces" in opc.body - assert "ClientNamespaces" not in self.opc.body + def test_namespaces(self, opc): + assert "ClientNamespaces" not in opc.body + + opc_with_namespaces = OPC_UA(rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"]) + assert "ClientNamespaces" in opc_with_namespaces.body + assert opc_with_namespaces.body["ClientNamespaces"] == ["1", "2"] def test_json_serial_with_url(self): url = Url("http://example.com") result = self.opc.json_serial(url) - self.assertEqual(result, "http://example.com/") - self.assertIsInstance(result, str) + assert result == "http://example.com/" + assert isinstance(result, str) def test_json_serial_with_datetime(self): dt = datetime(2024, 9, 2, 12, 0, 0) result = self.opc.json_serial(dt) - self.assertEqual(result, "2024-09-02T12:00:00") + assert result == "2024-09-02T12:00:00" def test_json_serial_with_date(self): d = date(2024, 9, 2) result = self.opc.json_serial(d) - self.assertEqual(result, "2024-09-02") + assert result == "2024-09-02" def test_json_serial_with_unsupported_type(self): unsupported_type = set() - with self.assertRaises(TypeError) as context: + with pytest.raises(TypeError) as exc_info: self.opc.json_serial(unsupported_type) - + expected_error_message = f"Type {type(unsupported_type)} not serializable" - self.assertEqual(str(context.exception), expected_error_message) + assert str(exc_info.value) == expected_error_message def test_check_auth_client_404_error(self): self.auth_client_mock.token.session_token = "test_token" @@ -581,19 +585,25 @@ def test_check_if_ory_session_token_is_valid_refresh_when_not_expired(self): self.auth_client_mock.refresh_token.assert_not_called() def test_get_value_type(self): - result_none = self.opc._get_value_type(100000) - assert result_none["id"] is None result = self.opc._get_value_type(1) assert "id" in result assert "type" in result assert "description" in result assert result["type"] == "Boolean" - def test_get_value_type_not_found(self): - result = self.opc._get_value_type(100000) - assert result["id"] is None - assert result["type"] is None - assert result["description"] is None + result = self.opc._get_value_type(25) + assert result["type"] == "DiagnosticInfo" + + result = self.opc._get_value_type(0) + assert result["id"] == 0 + assert result["type"] == "Null" + assert result["description"] == "An invalid or unspecified value" + + for invalid_type in [26, 100000]: + result = self.opc._get_value_type(invalid_type) + assert result["id"] is None + assert result["type"] is None + assert result["description"] is None def test_get_variable_list_as_list(self): pydantic_var = Variables(Id="SOMEID", Namespace=1, IdType=2) @@ -616,17 +626,14 @@ def test_get_values_variable_list_not_list(self): self.opc.get_values(not_a_list) def test_get_variable_list_as_list_invalid_type(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.opc._get_variable_list_as_list([1, 2, 3]) - @parameterized.expand([ - (None,), # Without auth client - (AUTH_CLIENT,) # With auth client - ]) + @pytest.mark.parametrize("auth_client_class", [None, AUTH_CLIENT]) @patch("requests.post", side_effect=successful_mocked_requests) - def test_get_live_values_successful_response_processing(self, AuthClientClass, mock_get): - if AuthClientClass: - auth_client = AuthClientClass( + def test_get_live_values_successful_response_processing(self, mock_get, auth_client_class): + if auth_client_class: + auth_client = auth_client_class( rest_url=URL, username="test_user", password="test_pass" ) auth_client.token = Token( @@ -640,30 +647,21 @@ def test_get_live_values_successful_response_processing(self, AuthClientClass, m result = tsdata.get_values(list_of_ids) - self.assertEqual(len(result), len(list_of_ids), "Result length should match input length") - + assert len(result) == len(list_of_ids), "Result length should match input length" + for num, row in enumerate(list_of_ids): - self.assertEqual(result[num]["Id"], list_of_ids[num]["Id"], "IDs should match") - self.assertEqual( - result[num]["Timestamp"], - successful_live_response[0]["Values"][num]["ServerTimestamp"], - "Timestamps should match" - ) - self.assertEqual( - result[num]["Value"], - successful_live_response[0]["Values"][num]["Value"]["Body"], - "Values should match" - ) - self.assertEqual( - result[num]["ValueType"], - tsdata._get_value_type( - successful_live_response[0]["Values"][num]["Value"]["Type"] - )["type"], - "Value types should match" - ) + assert result[num]["Id"] == list_of_ids[num]["Id"], "IDs should match" + assert ( + result[num]["Timestamp"] == + successful_live_response[0]["Values"][num]["ServerTimestamp"] + ), "Timestamps should match" + assert ( + result[num]["Value"] == + successful_live_response[0]["Values"][num]["Value"]["Body"] + ), "Values should match" @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_get_values_with_auth_client_error_handling(self, mock_request_from_api): + def test_get_live_values_with_auth_client_error_handling(self, mock_request_from_api): self.auth_client_mock.token.session_token = "test_token" mock_response = Mock() @@ -675,13 +673,11 @@ def test_get_values_with_auth_client_error_handling(self, mock_request_from_api) result = self.opc_auth.get_values(list_of_ids) - self.assertIsNotNone(result) - self.assertEqual(mock_request_from_api.call_count, 2, - "request_from_api should be called twice due to retry") + assert result is not None + assert mock_request_from_api.call_count == 2, "request_from_api should be called twice due to retry" - self.assertIn("Authorization", self.opc_auth.headers, "Authorization header is missing") - self.assertIn("test_token", self.opc_auth.headers.get("Authorization", ""), - "Session token not found in Authorization header") + assert "Authorization" in self.opc_auth.headers, "Authorization header is missing" + assert "test_token" in self.opc_auth.headers.get("Authorization", ""), "Session token not found in Authorization header" @patch("requests.post", side_effect=empty_values_mocked_requests) def test_get_live_values_with_missing_values(self, mock_get): @@ -722,20 +718,106 @@ def test_get_live_values_empty_response(self, mock_get): @patch('pyprediktormapclient.opc_ua.request_from_api') def test_get_live_values_error_handling(self, mock_request): mock_request.side_effect = Exception("Test exception") - with self.assertRaises(RuntimeError) as context: + with pytest.raises(RuntimeError) as exc_info: self.opc.get_values(list_of_ids) - self.assertEqual(str(context.exception), "Error in get_values: Test exception") + assert str(exc_info.value) == "Error in get_values: Test exception" @patch("requests.post", side_effect=unsuccessful_mocked_requests) def test_get_live_values_unsuccessful(self, mock_post): with pytest.raises(RuntimeError): self.opc.get_values(list_of_ids) + def test_check_content_valid(self): + valid_content = successful_historical_result.copy() + self.opc._check_content(valid_content) + + def test_check_content_not_dict(self): + with pytest.raises(RuntimeError, match="No content returned from the server"): + self.opc._check_content("Not a dictionary") + + def test_check_content_not_successful(self): + unsuccessful_content = successful_historical_result.copy() + unsuccessful_content["Success"] = False + unsuccessful_content["ErrorMessage"] = "Some error occurred" + + with pytest.raises(RuntimeError, match="Some error occurred"): + self.opc._check_content(unsuccessful_content) + + def test_check_content_no_history_results(self): + content_without_history = successful_historical_result.copy() + del content_without_history["HistoryReadResults"] + + with pytest.raises(RuntimeError, match="No history read results returned from the server"): + self.opc._check_content(content_without_history) + + def test_process_df_with_value_type(self): + df_input = pd.DataFrame({ + "Value.Type": [11, 12, 11], + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + "OldColumn2": [1, 2, 3] + }) + columns = {"OldColumn1": "NewColumn1", "OldColumn2": "NewColumn2"} + + result = self.opc._process_df(df_input, columns) + + assert list(result["Value.Type"]) == ["Double", "String", "Double"] + assert set(result.columns) == {"Value.Type", "Value.Body", "NewColumn1", "NewColumn2"} + + def test_process_df_without_value_type(self): + df_input = pd.DataFrame({ + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + "OldColumn2": [1, 2, 3] + }) + columns = {"OldColumn1": "NewColumn1", "OldColumn2": "NewColumn2"} + + result = self.opc._process_df(df_input, columns) + + assert set(result.columns) == {"Value.Body", "NewColumn1", "NewColumn2"} + + def test_process_df_rename_error(self): + df_input = pd.DataFrame({ + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + }) + columns = {"NonExistentColumn": "NewColumn"} + + with pytest.raises(KeyError): + self.opc._process_df(df_input, columns) + + def test_process_df_empty_dataframe(self): + df_input = pd.DataFrame() + columns = {} + + result = self.opc._process_df(df_input, columns) + + assert result.empty + assert isinstance(result, pd.DataFrame) + + def test_process_content(self): + result = self.opc._process_content(successful_historical_result) + + assert isinstance(result, pd.DataFrame) + assert len(result) == 4 + + assert "HistoryReadResults.NodeId.Id" in result.columns + assert "HistoryReadResults.NodeId.Namespace" in result.columns + assert "HistoryReadResults.NodeId.IdType" in result.columns + + assert result.iloc[0]["HistoryReadResults.NodeId.Id"] == "SOMEID" + assert result.iloc[0]["Value.Body"] == 34.28500000000003 + assert result.iloc[0]["SourceTimestamp"] == "2022-09-13T13:39:51Z" + + assert result.iloc[2]["HistoryReadResults.NodeId.Id"] == "SOMEID2" + assert result.iloc[2]["Value.Body"] == 34.28500000000003 + assert result.iloc[2]["SourceTimestamp"] == "2022-09-13T13:39:51Z" + @patch('pyprediktormapclient.opc_ua.request_from_api') def test_write_values_with_non_dict_content(self, mock_request): mock_request.return_value = "Not a dict" result = self.opc.write_values([{"NodeId": {"Id": "test", "Namespace": 1, "IdType": 2}, "Value": {"Value": {"Type": 10, "Body": 1.2}, "SourceTimestamp": "2022-11-03T12:00:00Z"}}]) - self.assertIsNone(result) + assert result is None def test_write_historical_values_empty_update_values(self): empty_variable = WriteHistoricalVariables( @@ -746,7 +828,7 @@ def test_write_historical_values_empty_update_values(self): with patch('pyprediktormapclient.opc_ua.request_from_api') as mock_request: mock_request.return_value = {"Success": True, "HistoryUpdateResults": [{}]} result = self.opc.write_historical_values([empty_variable]) - self.assertIsNotNone(result) + assert result is not None @patch('pyprediktormapclient.opc_ua.request_from_api') def test_write_values_http_error_handling(self, mock_request): @@ -763,8 +845,8 @@ def test_write_values_http_error_handling(self, mock_request): result = opc.write_values(list_of_write_values) opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) - self.assertIsNotNone(result) - self.assertEqual(len(result), len(list_of_write_values)) + assert result is not None + assert len(result) == len(list_of_write_values) @patch('pyprediktormapclient.opc_ua.request_from_api') def test_write_historical_values_http_error_handling(self, mock_request): @@ -782,15 +864,15 @@ def test_write_historical_values_http_error_handling(self, mock_request): result = opc.write_historical_values(converted_data) opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) - self.assertIsNotNone(result) - self.assertEqual(len(result), len(converted_data)) + assert result is not None + assert len(result) == len(converted_data) @patch('pyprediktormapclient.opc_ua.request_from_api') def test_error_handling_write_values(self, mock_request): mock_request.side_effect = Exception("Test exception") - with self.assertRaises(RuntimeError) as context: + with pytest.raises(RuntimeError) as exc_info: self.opc.write_values(list_of_write_values) - self.assertEqual(str(context.exception), "Error in write_values: Test exception") + assert str(exc_info.value) == "Error in write_values: Test exception" @patch('pyprediktormapclient.opc_ua.request_from_api') def test_error_handling_write_historical_values(self, mock_request): @@ -798,17 +880,15 @@ def test_error_handling_write_historical_values(self, mock_request): Exception("Test exception"), Exception("Test exception") ] - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) input_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] - with self.assertRaises(Exception) as context: - opc.write_historical_values(input_data) - self.assertEqual(str(context.exception), "Test exception") - self.assertEqual(mock_request.call_count, 2) + with pytest.raises(Exception) as exc_info: + self.opc.write_historical_values(input_data) + assert str(exc_info.value) == "Test exception" + assert mock_request.call_count == 2 @patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.write_values(list_of_write_values) + result = self.opc.write_values(list_of_write_values) for num, row in enumerate(list_of_write_values): assert ( result[num]["Value"]["StatusCode"]["Code"] @@ -826,72 +906,36 @@ def test_write_live_values_successful(self, mock_get): def test_write_live_values_with_missing_value_and_statuscode( self, mock_get ): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.write_values(list_of_write_values) + result = self.opc.write_values(list_of_write_values) for num, row in enumerate(list_of_write_values): assert result[num]["WriteSuccess"] is False @patch("requests.post", side_effect=no_write_mocked_requests) def test_get_write_live_values_no_response(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = tsdata.write_values(list_of_write_values) + result = self.opc.write_values(list_of_write_values) assert result is None @patch( "requests.post", side_effect=unsuccessful_write_mocked_requests ) def test_get_write_live_values_unsuccessful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with pytest.raises(RuntimeError): - tsdata.write_values(list_of_write_values) + self.opc.write_values(list_of_write_values) @patch("requests.post", side_effect=empty_write_mocked_requests) def test_get_write_live_values_empty(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) with pytest.raises(ValueError): - tsdata.write_values(list_of_write_values) - - def test_process_content(self): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - - content = { - "Success": True, - "HistoryReadResults": [ - { - "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, - "DataValues": [ - {"Value": {"Type": 11, "Body": 1.23}, "SourceTimestamp": "2023-01-01T00:00:00Z"} - ] - } - ] - } - result = opc._process_content(content) - assert isinstance(result, pd.DataFrame) - assert len(result) == 1 - assert result.iloc[0]["Value.Body"] == 1.23 - - with pytest.raises(RuntimeError, match="No content returned from the server"): - opc._process_content(None) - - content = {"Success": False, "ErrorMessage": "Error"} - with pytest.raises(RuntimeError, match="Error"): - opc._process_content(content) - - content = {"Success": True} - with pytest.raises(RuntimeError, match="No history read results returned from the server"): - opc._process_content(content) + self.opc.write_values(list_of_write_values) @patch("requests.post") def test_get_values_unsuccessful(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = Mock() error_response.status_code = 500 error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") mock_post.return_value = error_response with pytest.raises(RuntimeError): - opc.get_values(list_of_ids) + self.opc.get_values(list_of_ids) @parameterized.expand([ (WriteVariables, list_of_write_values, "write_values"), @@ -899,8 +943,6 @@ def test_get_values_unsuccessful(self, mock_post): ]) @patch("requests.post") def test_write_no_content(self, VariableClass, list_of_values, write_method, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - no_content_response = Mock() no_content_response.status_code = 200 no_content_response.json.return_value = {"Success": True} @@ -910,7 +952,7 @@ def test_write_no_content(self, VariableClass, list_of_values, write_method, moc converted_data = [VariableClass(**item) for item in list_of_values] with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): - getattr(opc, write_method)(converted_data) + getattr(self.opc, write_method)(converted_data) @parameterized.expand([ (WriteVariables, list_of_write_values, "write_values", 500), @@ -918,8 +960,6 @@ def test_write_no_content(self, VariableClass, list_of_values, write_method, moc ]) @patch("requests.post") def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, status_code, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = Mock() error_response.status_code = status_code error_response.json.return_value = {"Success": False, "ErrorMessage": "Error"} @@ -929,12 +969,10 @@ def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, s converted_data = [VariableClass(**item) for item in list_of_values] with pytest.raises(RuntimeError, match="Error"): - getattr(opc, write_method)(converted_data) + getattr(self.opc, write_method)(converted_data) @patch("requests.post") def test_write_values_no_status_codes(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - error_response = Mock() error_response.status_code = 200 error_response.json.return_value = {"Success": True} @@ -942,19 +980,18 @@ def test_write_values_no_status_codes(self, mock_post): mock_post.return_value = error_response with pytest.raises(ValueError): - opc.write_values(list_of_write_values) + self.opc.write_values(list_of_write_values) @patch( "requests.post", side_effect=successful_write_historical_mocked_requests, ) def test_write_historical_values_successful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values ] - result = tsdata.write_historical_values(converted_data) + result = self.opc.write_historical_values(converted_data) for num, row in enumerate(list_of_write_values): assert result[0]["WriteSuccess"] is True @@ -964,13 +1001,12 @@ def test_write_historical_values_successful(self, mock_get): side_effect=successful_write_historical_mocked_requests, ) def test_write_wrong_order_historical_values_successful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values_in_wrong_order ] with pytest.raises(ValueError): - tsdata.write_historical_values(converted_data) + self.opc.write_historical_values(converted_data) @patch( "requests.post", side_effect=empty_write_historical_mocked_requests @@ -978,24 +1014,22 @@ def test_write_wrong_order_historical_values_successful(self, mock_get): def test_write_historical_values_with_missing_value_and_statuscode( self, mock_get ): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values ] with pytest.raises(ValueError): - tsdata.write_historical_values(converted_data) + self.opc.write_historical_values(converted_data) @patch( "requests.post", side_effect=no_write_mocked_historical_requests ) def test_get_write_historical_values_no_response(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values ] - result = tsdata.write_historical_values(converted_data) + result = self.opc.write_historical_values(converted_data) assert result is None @patch( @@ -1003,13 +1037,12 @@ def test_get_write_historical_values_no_response(self, mock_get): side_effect=unsuccessful_write_historical_mocked_requests, ) def test_get_write_historical_values_unsuccessful(self, mock_get): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values ] with pytest.raises(RuntimeError): - tsdata.write_historical_values(converted_data) + self.opc.write_historical_values(converted_data) @patch( "requests.post", @@ -1018,12 +1051,11 @@ def test_get_write_historical_values_unsuccessful(self, mock_get): def test_get_write_historical_values_successful_with_error_codes( self, mock_get ): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) converted_data = [ WriteHistoricalVariables(**item) for item in list_of_historical_values_wrong_type_and_value ] - result = tsdata.write_historical_values(converted_data) + result = self.opc.write_historical_values(converted_data) assert ( result[0]["WriteError"]["Code"] == successfull_write_historical_response_with_errors[ @@ -1039,19 +1071,15 @@ def test_get_write_historical_values_successful_with_error_codes( @patch("requests.post") def test_write_historical_values_no_history_update_results(self, mock_request): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - converted_data = [ WriteHistoricalVariables(**item) for item in list_of_write_historical_values ] with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): - opc.write_historical_values(converted_data) + self.opc.write_historical_values(converted_data) @patch("requests.post") def test_write_historical_values_wrong_order(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - wrong_order_response = Mock() wrong_order_response.status_code = 200 wrong_order_response.json.return_value = {"Success": False, "ErrorMessage": "UpdateValues attribute missing"} @@ -1064,7 +1092,7 @@ def test_write_historical_values_wrong_order(self, mock_post): ] with pytest.raises(ValueError, match="Time for variables not in correct order."): - opc.write_historical_values(converted_data) + self.opc.write_historical_values(converted_data) class AsyncMockResponse: @@ -1099,9 +1127,8 @@ def unsuccessful_async_mock_response(*args, **kwargs): return AsyncMockResponse(json_data=None, status_code=400) -async def make_historical_request(): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - return await tsdata.get_historical_aggregated_values_asyn( +async def make_historical_request(opc): + return await opc.get_historical_aggregated_values_asyn( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), pro_interval=3600000, @@ -1110,9 +1137,8 @@ async def make_historical_request(): ) -async def make_raw_historical_request(): - tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - return await tsdata.get_raw_historical_values_asyn( +async def make_raw_historical_request(opc): + return await opc.get_raw_historical_values_asyn( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), variable_list=list_of_ids, @@ -1120,13 +1146,15 @@ async def make_raw_historical_request(): @pytest.mark.asyncio -class TestCaseAsyncOPCUA(): +class TestCaseAsyncOPCUA: + @pytest.fixture(autouse=True) + def setup(self, opc): + self.opc = opc + yield @patch("aiohttp.ClientSession.post") - async def test_make_request_retries(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - - error_response = Mock() + async def test_make_request_max_retries(self, mock_post): + error_response = AsyncMock() error_response.status = 500 error_response.raise_for_status.side_effect = ClientResponseError( request_info=aiohttp.RequestInfo( @@ -1139,79 +1167,76 @@ async def test_make_request_retries(self, mock_post): status=500 ) - mock_post.side_effect = [error_response, error_response, error_response] + mock_post.side_effect = [error_response] * 3 + + with pytest.raises(RuntimeError) as exc_info: + await self.opc._make_request("test_endpoint", {}, 3, 0) - with pytest.raises(RuntimeError): - await opc._make_request("test_endpoint", {}, 3, 0) assert mock_post.call_count == 3 + assert "Max retries reached" in str(exc_info.value) @patch('aiohttp.ClientSession.post') async def test_make_request_client_error(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - mock_post.side_effect = aiohttp.ClientError("Test client error") with pytest.raises(RuntimeError): - await opc._make_request("test_endpoint", {}, 1, 0) + await self.opc._make_request("test_endpoint", {}, 1, 0) @patch("aiohttp.ClientSession.post") - async def test_make_request_500_error(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) + async def test_make_request_500_error_no_retry(self, mock_post, mock_error_response): + mock_error_response.status = 500 + mock_error_response.text.return_value = "Internal Server Error" + mock_error_response.raise_for_status.side_effect.status = 500 + mock_error_response.raise_for_status.side_effect.message = "Internal Server Error" + mock_post.return_value.__aenter__.return_value = mock_error_response - error_response = AsyncMock() - error_response.status = 500 + with pytest.raises(ClientResponseError) as exc_info: + await self.opc._make_request("test_endpoint", {}, 3, 0) - async def raise_for_status(): - raise ClientResponseError( - request_info=aiohttp.RequestInfo( - url=YarlURL("http://example.com"), - method="POST", - headers={}, - real_url=YarlURL("http://example.com") - ), - history=(), - status=500 - ) - error_response.raise_for_status.side_effect = raise_for_status - mock_post.return_value.__aenter__.return_value = error_response + assert exc_info.value.status == 500 + assert "Internal Server Error" in str(exc_info.value) + assert mock_post.call_count == 1 - with pytest.raises(ClientResponseError): - await error_response.raise_for_status() - await opc._make_request("test_endpoint", {}, 1, 0) + @patch("aiohttp.ClientSession.post") + @patch("asyncio.sleep", return_value=None) + async def test_make_request_non_500_error_with_retry(self, mock_sleep, mock_post, mock_error_response): + mock_error_response.status = 400 + mock_error_response.text.return_value = "Bad Request" + mock_error_response.raise_for_status.side_effect.status = 400 + mock_error_response.raise_for_status.side_effect.message = "Bad Request" + + + success_response = AsyncMock() + success_response.status = 200 + success_response.json.return_value = {"success": True} + + mock_post.side_effect = [ + AsyncMock(__aenter__=AsyncMock(return_value=mock_error_response)), + AsyncMock(__aenter__=AsyncMock(return_value=mock_error_response)), + AsyncMock(__aenter__=AsyncMock(return_value=success_response)) + ] - @patch('aiohttp.ClientSession.post') - async def test_make_request_max_retries_reached(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - - error_response = Mock() - error_response.status = 500 - error_response.raise_for_status.side_effect = ClientResponseError( - request_info=aiohttp.RequestInfo( - url=YarlURL("http://example.com"), - method="POST", - headers={}, - real_url=YarlURL("http://example.com") - ), - history=(), - status=500 - ) - - mock_post.side_effect = [error_response, error_response, error_response] - - with pytest.raises(RuntimeError): - await opc._make_request("test_endpoint", {}, 3, 0) + result = await self.opc._make_request("test_endpoint", {}, 3, 0) + + assert result == {"success": True} assert mock_post.call_count == 3 + assert mock_sleep.call_count == 2 + + mock_error_response.text.assert_awaited() + mock_error_response.raise_for_status.assert_called() + + success_response.json.assert_awaited() @patch("aiohttp.ClientSession.post") async def test_make_request_successful(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - mock_response = Mock() + mock_response = AsyncMock() mock_response.status = 200 - mock_response.json = AsyncMock(return_value={"Success": True}) + mock_response.json.return_value = {"Success": True} mock_post.return_value.__aenter__.return_value = mock_response - result = await opc._make_request("test_endpoint", {}, 3, 0) + result = await self.opc._make_request("test_endpoint", {}, 3, 0) assert result == {"Success": True} + assert mock_post.call_count == 1 @patch("aiohttp.ClientSession.post") async def test_historical_values_success(self, mock_post): @@ -1238,7 +1263,6 @@ async def test_historical_values_success(self, mock_post): @patch("pyprediktormapclient.opc_ua.OPC_UA._make_request") async def test_get_historical_values(self, mock_make_request): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) mock_make_request.return_value = { "Success": True, "HistoryReadResults": [ @@ -1255,7 +1279,7 @@ async def test_get_historical_values(self, mock_make_request): end_time = datetime(2023, 1, 2) variable_list = ["SOMEID"] - result = await opc.get_historical_values( + result = await self.opc.get_historical_values( start_time, end_time, variable_list, "test_endpoint", lambda vars: [{"NodeId": var} for var in vars] ) @@ -1266,13 +1290,11 @@ async def test_get_historical_values(self, mock_make_request): @patch("aiohttp.ClientSession.post") async def test_get_historical_values_no_results(self, mock_post): - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - mock_post.return_value = AsyncMockResponse( json_data={"Success": True, "HistoryReadResults": []}, status_code=200 ) - result = await opc.get_historical_values( + result = await self.opc.get_historical_values( start_time=datetime(2023, 1, 1), end_time=datetime(2023, 1, 2), variable_list=["SOMEID"], @@ -1304,8 +1326,7 @@ async def test_get_historical_aggregated_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_historical_result, status_code=200 ) - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = await opc.get_historical_aggregated_values_asyn( + result = await self.opc.get_historical_aggregated_values_asyn( start_time=datetime(2023, 1, 1), end_time=datetime(2023, 1, 2), pro_interval=3600000, From 1d85886149646fc2985fdab80f1208d9da876b9b Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 9 Sep 2024 14:35:48 +0200 Subject: [PATCH 15/35] Achieved 100% code coverage for the OPC UA class. --- src/pyprediktormapclient/opc_ua.py | 43 ++-- tests/opc_ua_test.py | 384 +++++++++++++++++------------ 2 files changed, 252 insertions(+), 175 deletions(-) diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 4100c59..6231994 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -408,10 +408,9 @@ async def _make_request( return await response.json() except aiohttp.ClientResponseError as e: - if e.status == 500 or attempt == max_retries - 1: - logging.error(f"Server Error: {e}") - raise # Raise for 500 errors or on last attempt logging.error(f"ClientResponseError: {e}") + if attempt == max_retries - 1: + raise RuntimeError("Max retries reached") from e except aiohttp.ClientError as e: logging.error(f"ClientError in POST request: {e}") except Exception as e: @@ -423,9 +422,9 @@ async def _make_request( f"Request failed. Retrying in {wait_time} seconds..." ) await asyncio.sleep(wait_time) - else: - logging.error("Max retries reached.") - raise RuntimeError("Max retries reached") + + logging.error("Max retries reached.") + raise RuntimeError("Max retries reached") def _process_content(self, content: dict) -> pd.DataFrame: self._check_content(content) @@ -667,13 +666,13 @@ def write_historical_values( """ # Check if data is in correct order, if wrong fail. for variable in variable_list: - if len(variable.UpdateValues) > 1: - for num_variable in range(len(variable.UpdateValues) - 1): + if len(variable.get('UpdateValues', [])) > 1: + for num_variable in range(len(variable['UpdateValues']) - 1): if not ( - (variable.UpdateValues[num_variable].SourceTimestamp) - < variable.UpdateValues[ + (variable['UpdateValues'][num_variable]['SourceTimestamp']) + < variable['UpdateValues'][ num_variable + 1 - ].SourceTimestamp + ]['SourceTimestamp'] ): raise ValueError( "Time for variables not in correct order." @@ -694,17 +693,19 @@ def write_historical_values( except HTTPError as e: if self.auth_client is not None: self.check_auth_client(json.loads(e.response.content)) + # Retry the request after checking auth + content = request_from_api( + rest_url=self.rest_url, + method="POST", + endpoint="values/historicalwrite", + data=json.dumps(body, default=self.json_serial), + headers=self.headers, + extended_timeout=True, + ) else: - raise RuntimeError(f"Error message {e}") - finally: - content = request_from_api( - rest_url=self.rest_url, - method="POST", - endpoint="values/historicalwrite", - data=json.dumps(body, default=self.json_serial), - headers=self.headers, - extended_timeout=True, - ) + raise RuntimeError(f"Error in write_historical_values: {str(e)}") + except Exception as e: + raise RuntimeError(f"Error in write_historical_values: {str(e)}") # Return if no content from server if not isinstance(content, dict): return None diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 7707078..70c8c49 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -520,6 +520,24 @@ def test_namespaces(self, opc): assert "ClientNamespaces" in opc_with_namespaces.body assert opc_with_namespaces.body["ClientNamespaces"] == ["1", "2"] + def test_init_with_auth_client(self): + self.auth_client_mock.token = Mock() + self.auth_client_mock.token.session_token = "test_token" + self.opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + + assert self.opc_auth.auth_client == self.auth_client_mock + assert self.opc_auth.headers["Authorization"] == "Bearer test_token" + + def test_init_with_auth_client_no_token(self): + self.auth_client_mock.token = None + opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + + assert "Authorization" not in opc_auth.headers + + def test_init_with_minimal_args(self): + assert self.opc.auth_client is None + assert "Authorization" not in self.opc.headers + def test_json_serial_with_url(self): url = Url("http://example.com") result = self.opc.json_serial(url) @@ -722,6 +740,16 @@ def test_get_live_values_error_handling(self, mock_request): self.opc.get_values(list_of_ids) assert str(exc_info.value) == "Error in get_values: Test exception" + @patch("requests.post") + def test_get_live_values_500_error(self, mock_post): + error_response = Mock() + error_response.status_code = 500 + error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") + + mock_post.return_value = error_response + with pytest.raises(RuntimeError): + self.opc.get_values(list_of_ids) + @patch("requests.post", side_effect=unsuccessful_mocked_requests) def test_get_live_values_unsuccessful(self, mock_post): with pytest.raises(RuntimeError): @@ -814,24 +842,7 @@ def test_process_content(self): assert result.iloc[2]["SourceTimestamp"] == "2022-09-13T13:39:51Z" @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_write_values_with_non_dict_content(self, mock_request): - mock_request.return_value = "Not a dict" - result = self.opc.write_values([{"NodeId": {"Id": "test", "Namespace": 1, "IdType": 2}, "Value": {"Value": {"Type": 10, "Body": 1.2}, "SourceTimestamp": "2022-11-03T12:00:00Z"}}]) - assert result is None - - def test_write_historical_values_empty_update_values(self): - empty_variable = WriteHistoricalVariables( - NodeId=Variables(Id="test", Namespace=1, IdType=2), - PerformInsertReplace=1, - UpdateValues=[] - ) - with patch('pyprediktormapclient.opc_ua.request_from_api') as mock_request: - mock_request.return_value = {"Success": True, "HistoryUpdateResults": [{}]} - result = self.opc.write_historical_values([empty_variable]) - assert result is not None - - @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_write_values_http_error_handling(self, mock_request): + def test_write_live_values_http_error_handling(self, mock_request): auth_client_mock = Mock() opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) @@ -849,42 +860,19 @@ def test_write_values_http_error_handling(self, mock_request): assert len(result) == len(list_of_write_values) @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_write_historical_values_http_error_handling(self, mock_request): - auth_client_mock = Mock() - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - - mock_response = Mock() - mock_response.content = json.dumps({"error": {"code": 404}}).encode() - http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) - mock_request.side_effect = [http_error, {"Success": True, "HistoryUpdateResults": [{}]}] - - opc.check_auth_client = Mock() - - converted_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] - result = opc.write_historical_values(converted_data) - - opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) - assert result is not None - assert len(result) == len(converted_data) - - @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_error_handling_write_values(self, mock_request): + def test_write_live_values_error_handling(self, mock_request): mock_request.side_effect = Exception("Test exception") with pytest.raises(RuntimeError) as exc_info: self.opc.write_values(list_of_write_values) assert str(exc_info.value) == "Error in write_values: Test exception" @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_error_handling_write_historical_values(self, mock_request): - mock_request.side_effect = [ - Exception("Test exception"), - Exception("Test exception") - ] - input_data = [WriteHistoricalVariables(**item) for item in list_of_write_historical_values] - with pytest.raises(Exception) as exc_info: - self.opc.write_historical_values(input_data) - assert str(exc_info.value) == "Test exception" - assert mock_request.call_count == 2 + def test_write_live_values_http_error_without_auth(self, mock_request): + mock_request.side_effect = requests.exceptions.HTTPError("404 Client Error") + + with pytest.raises(RuntimeError) as exc_info: + self.opc.write_values(list_of_write_values) + assert str(exc_info.value).startswith("Error in write_values: 404 Client Error") @patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): @@ -927,60 +915,57 @@ def test_get_write_live_values_empty(self, mock_get): with pytest.raises(ValueError): self.opc.write_values(list_of_write_values) - @patch("requests.post") - def test_get_values_unsuccessful(self, mock_post): - error_response = Mock() - error_response.status_code = 500 - error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") - - mock_post.return_value = error_response - with pytest.raises(RuntimeError): - self.opc.get_values(list_of_ids) - - @parameterized.expand([ - (WriteVariables, list_of_write_values, "write_values"), - (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values") - ]) - @patch("requests.post") - def test_write_no_content(self, VariableClass, list_of_values, write_method, mock_post): - no_content_response = Mock() - no_content_response.status_code = 200 - no_content_response.json.return_value = {"Success": True} - no_content_response.headers = {"Content-Type": "application/json"} - - mock_post.return_value = no_content_response - converted_data = [VariableClass(**item) for item in list_of_values] - - with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): - getattr(self.opc, write_method)(converted_data) - - @parameterized.expand([ - (WriteVariables, list_of_write_values, "write_values", 500), - (WriteHistoricalVariables, list_of_write_historical_values, "write_historical_values", 200) - ]) - @patch("requests.post") - def test_write_unsuccessful(self, VariableClass, list_of_values, write_method, status_code, mock_post): - error_response = Mock() - error_response.status_code = status_code - error_response.json.return_value = {"Success": False, "ErrorMessage": "Error"} - error_response.headers = {"Content-Type": "application/json"} + def test_write_historical_values_succeeds_with_empty_update_values(self): + empty_historical_variable = WriteHistoricalVariables( + NodeId=Variables(**list_of_write_values[0]["NodeId"]), + PerformInsertReplace=1, + UpdateValues=[] + ) + + with patch('pyprediktormapclient.opc_ua.request_from_api') as mock_request: + mock_request.return_value = {"Success": True, "HistoryUpdateResults": [{}]} + result = self.opc.write_historical_values([empty_historical_variable.model_dump()]) + + assert result is not None + assert len(result) == 1 + assert result[0].get('WriteSuccess', False) - mock_post.return_value = error_response - converted_data = [VariableClass(**item) for item in list_of_values] + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_historical_values_http_error_handling(self, mock_request): + mock_response = Mock() + mock_response.content = json.dumps({"error": {"code": 404}}).encode() + http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) + mock_request.side_effect = [http_error, {"Success": True, "HistoryUpdateResults": [{}]}] + + self.opc_auth.check_auth_client = Mock() + + converted_data = [WriteHistoricalVariables(**item).model_dump() + for item in list_of_write_historical_values] + result = self.opc_auth.write_historical_values(converted_data) + + self.opc_auth.check_auth_client.assert_called_once_with({"error": {"code": 404}}) + assert result is not None + assert len(result) == len(converted_data) - with pytest.raises(RuntimeError, match="Error"): - getattr(self.opc, write_method)(converted_data) + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_error_handling_write_historical_values(self, mock_request): + mock_request.side_effect = [ + Exception("Test exception"), + Exception("Test exception") + ] + input_data = [WriteHistoricalVariables(**item).model_dump() + for item in list_of_write_historical_values] + with pytest.raises(Exception) as exc_info: + self.opc.write_historical_values(input_data) + assert str(exc_info.value) == "Error in write_historical_values: Test exception" + assert mock_request.call_count == 1 - @patch("requests.post") - def test_write_values_no_status_codes(self, mock_post): - error_response = Mock() - error_response.status_code = 200 - error_response.json.return_value = {"Success": True} - error_response.headers = {"Content-Type": "application/json"} - - mock_post.return_value = error_response - with pytest.raises(ValueError): - self.opc.write_values(list_of_write_values) + @patch('pyprediktormapclient.opc_ua.request_from_api') + def test_write_historical_values_http_error_without_auth(self, mock_request): + mock_request.side_effect = requests.exceptions.HTTPError("404 Client Error") + with pytest.raises(RuntimeError) as exc_info: + self.opc.write_historical_values(list_of_write_historical_values) + assert str(exc_info.value) == "Error in write_historical_values: 404 Client Error" @patch( "requests.post", @@ -988,12 +973,11 @@ def test_write_values_no_status_codes(self, mock_post): ) def test_write_historical_values_successful(self, mock_get): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values ] result = self.opc.write_historical_values(converted_data) - for num, row in enumerate(list_of_write_values): - assert result[0]["WriteSuccess"] is True + assert all(row.get("WriteSuccess", False) for row in result) @patch( @@ -1002,7 +986,7 @@ def test_write_historical_values_successful(self, mock_get): ) def test_write_wrong_order_historical_values_successful(self, mock_get): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values_in_wrong_order ] with pytest.raises(ValueError): @@ -1015,7 +999,7 @@ def test_write_historical_values_with_missing_value_and_statuscode( self, mock_get ): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values ] with pytest.raises(ValueError): @@ -1026,7 +1010,7 @@ def test_write_historical_values_with_missing_value_and_statuscode( ) def test_get_write_historical_values_no_response(self, mock_get): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values ] result = self.opc.write_historical_values(converted_data) @@ -1038,7 +1022,7 @@ def test_get_write_historical_values_no_response(self, mock_get): ) def test_get_write_historical_values_unsuccessful(self, mock_get): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values ] with pytest.raises(RuntimeError): @@ -1052,7 +1036,7 @@ def test_get_write_historical_values_successful_with_error_codes( self, mock_get ): converted_data = [ - WriteHistoricalVariables(**item) + WriteHistoricalVariables(**item).model_dump() for item in list_of_historical_values_wrong_type_and_value ] result = self.opc.write_historical_values(converted_data) @@ -1069,30 +1053,14 @@ def test_get_write_historical_values_successful_with_error_codes( ][0]["StatusCode"]["Symbol"] ) - @patch("requests.post") - def test_write_historical_values_no_history_update_results(self, mock_request): - converted_data = [ - WriteHistoricalVariables(**item) - for item in list_of_write_historical_values - ] - with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): - self.opc.write_historical_values(converted_data) - - @patch("requests.post") - def test_write_historical_values_wrong_order(self, mock_post): - wrong_order_response = Mock() - wrong_order_response.status_code = 200 - wrong_order_response.json.return_value = {"Success": False, "ErrorMessage": "UpdateValues attribute missing"} - wrong_order_response.headers = {"Content-Type": "application/json"} - - mock_post.return_value = wrong_order_response - converted_data = [ - WriteHistoricalVariables(**item) - for item in list_of_write_historical_values_in_wrong_order - ] - - with pytest.raises(ValueError, match="Time for variables not in correct order."): - self.opc.write_historical_values(converted_data) + # @patch("requests.post") + # def test_write_historical_values_no_history_update_results(self, mock_request): + # converted_data = [ + # WriteHistoricalVariables(**item) + # for item in list_of_write_historical_values + # ] + # with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): + # self.opc.write_historical_values(converted_data) class AsyncMockResponse: @@ -1152,6 +1120,22 @@ def setup(self, opc): self.opc = opc yield + async def test_run_coroutine(self): + async def sample_coroutine(): + await asyncio.sleep(0.1) + return "Hello, World!" + + result = self.opc.helper.run_coroutine(sample_coroutine()) + assert result == "Hello, World!" + + async def test_run_coroutine_with_exception(self): + async def failing_coroutine(): + await asyncio.sleep(0.1) + raise ValueError("Test exception") + + with pytest.raises(ValueError, match="Test exception"): + self.opc.helper.run_coroutine(failing_coroutine()) + @patch("aiohttp.ClientSession.post") async def test_make_request_max_retries(self, mock_post): error_response = AsyncMock() @@ -1175,6 +1159,32 @@ async def test_make_request_max_retries(self, mock_post): assert mock_post.call_count == 3 assert "Max retries reached" in str(exc_info.value) + @patch("aiohttp.ClientSession.post") + @patch("asyncio.sleep", return_value=None) + async def test_make_request_all_retries_fail(self, mock_sleep, mock_post): + error_response = AsyncMock() + error_response.status = 400 + error_response.text.return_value = "Bad Request" + error_response.raise_for_status.side_effect = ClientResponseError( + request_info=aiohttp.RequestInfo( + url=YarlURL("http://example.com"), + method="POST", + headers={}, + real_url=YarlURL("http://example.com") + ), + history=(), + status=400 + ) + mock_post.return_value.__aenter__.return_value = error_response + max_retries = 3 + retry_delay = 0 + + with pytest.raises(RuntimeError, match="Max retries reached"): + await self.opc._make_request("test_endpoint", {}, max_retries, retry_delay) + + assert mock_post.call_count == max_retries + assert mock_sleep.call_count == max_retries - 1 + @patch('aiohttp.ClientSession.post') async def test_make_request_client_error(self, mock_post): mock_post.side_effect = aiohttp.ClientError("Test client error") @@ -1182,21 +1192,6 @@ async def test_make_request_client_error(self, mock_post): with pytest.raises(RuntimeError): await self.opc._make_request("test_endpoint", {}, 1, 0) - @patch("aiohttp.ClientSession.post") - async def test_make_request_500_error_no_retry(self, mock_post, mock_error_response): - mock_error_response.status = 500 - mock_error_response.text.return_value = "Internal Server Error" - mock_error_response.raise_for_status.side_effect.status = 500 - mock_error_response.raise_for_status.side_effect.message = "Internal Server Error" - mock_post.return_value.__aenter__.return_value = mock_error_response - - with pytest.raises(ClientResponseError) as exc_info: - await self.opc._make_request("test_endpoint", {}, 3, 0) - - assert exc_info.value.status == 500 - assert "Internal Server Error" in str(exc_info.value) - assert mock_post.call_count == 1 - @patch("aiohttp.ClientSession.post") @patch("asyncio.sleep", return_value=None) async def test_make_request_non_500_error_with_retry(self, mock_sleep, mock_post, mock_error_response): @@ -1243,7 +1238,7 @@ async def test_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_historical_result, status_code=200 ) - result = await make_historical_request() + result = await make_historical_request(opc=self.opc) cols_to_check = ["Value"] assert all( ptypes.is_numeric_dtype(result[col]) for col in cols_to_check @@ -1321,6 +1316,38 @@ async def test_get_raw_historical_values_asyn(self, mock_post): assert "Value" in result.columns assert "Timestamp" in result.columns + async def test_get_raw_historical_values_success(self): + mock_result = AsyncMock() + + with patch.object(self.opc, 'get_raw_historical_values_asyn', return_value=mock_result): + with patch.object(self.opc.helper, 'run_coroutine', return_value=mock_result) as mock_run_coroutine: + result = self.opc.get_raw_historical_values( + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), + variable_list=list_of_ids, + ) + + mock_run_coroutine.assert_called_once() + assert result == mock_result + + async def test_get_raw_historical_values_with_args(self): + mock_result = AsyncMock() + + with patch.object(self.opc, 'get_raw_historical_values_asyn', return_value=mock_result) as mock_async: + result = await make_raw_historical_request(self.opc) + + mock_async.assert_called_once() + args, kwargs = mock_async.call_args + assert 'start_time' in kwargs + assert 'end_time' in kwargs + assert kwargs['variable_list'] == list_of_ids + assert result == mock_result + + async def test_get_raw_historical_values_exception(self): + with patch.object(self.opc, 'get_raw_historical_values_asyn', side_effect=Exception("Test exception")): + with pytest.raises(Exception, match="Test exception"): + await make_raw_historical_request(self.opc) + @patch("aiohttp.ClientSession.post") async def test_get_historical_aggregated_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( @@ -1338,28 +1365,77 @@ async def test_get_historical_aggregated_values_asyn(self, mock_post): assert "Timestamp" in result.columns assert "StatusSymbol" in result.columns + async def test_get_historical_aggregated_values_success(self): + mock_result = AsyncMock() + + with patch.object(self.opc, 'get_historical_aggregated_values_asyn', return_value=mock_result): + with patch.object(self.opc.helper, 'run_coroutine', return_value=mock_result) as mock_run_coroutine: + result = self.opc.get_historical_aggregated_values( + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), + pro_interval=3600000, + agg_name="Average", + variable_list=list_of_ids, + ) + + mock_run_coroutine.assert_called_once() + assert result == mock_result + + async def test_get_historical_aggregated_values_with_args(self): + mock_result = AsyncMock() + + with patch.object(self.opc, 'get_historical_aggregated_values_asyn', return_value=mock_result) as mock_async: + result = await self.opc.get_historical_aggregated_values_asyn( + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), + pro_interval=3600000, + agg_name="Average", + variable_list=list_of_ids, + ) + + mock_async.assert_called_once() + args, kwargs = mock_async.call_args + assert 'start_time' in kwargs + assert 'end_time' in kwargs + assert kwargs['pro_interval'] == 3600000 + assert kwargs['agg_name'] == "Average" + assert kwargs['variable_list'] == list_of_ids + assert result == mock_result + + async def test_get_historical_aggregated_values_exception(self): + with patch.object(self.opc, 'get_historical_aggregated_values_asyn', side_effect=Exception("Test exception")): + with patch.object(self.opc.helper, 'run_coroutine', side_effect=Exception("Test exception")): + with pytest.raises(Exception, match="Test exception"): + self.opc.get_historical_aggregated_values( + start_time=(datetime.now() - timedelta(30)), + end_time=(datetime.now() - timedelta(29)), + pro_interval=3600000, + agg_name="Average", + variable_list=list_of_ids, + ) + @patch("aiohttp.ClientSession.post") async def test_historical_values_no_dict(self, mock_post): with pytest.raises(RuntimeError): - await make_historical_request() + await make_historical_request(opc=self.opc) @patch("aiohttp.ClientSession.post") async def test_historical_values_unsuccess(self, mock_post): mock_post.return_value = unsuccessful_async_mock_response() with pytest.raises(RuntimeError): - await make_historical_request() + await make_historical_request(opc=self.opc) @patch("aiohttp.ClientSession.post") async def test_historical_values_no_hist(self, mock_post): with pytest.raises(RuntimeError): - await make_historical_request() + await make_historical_request(opc=self.opc) @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_success(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_raw_historical_result, status_code=200 ) - result = await make_raw_historical_request() + result = await make_raw_historical_request(opc=self.opc) cols_to_check = ["Value"] assert all( ptypes.is_numeric_dtype(result[col]) for col in cols_to_check @@ -1368,17 +1444,17 @@ async def test_raw_historical_values_success(self, mock_post): @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_no_dict(self, mock_post): with pytest.raises(RuntimeError): - await make_raw_historical_request() + await make_raw_historical_request(opc=self.opc) @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_unsuccess(self, mock_post): with pytest.raises(RuntimeError): - await make_raw_historical_request() + await make_raw_historical_request(opc=self.opc) @patch("aiohttp.ClientSession.post") async def test_raw_historical_values_no_hist(self, mock_post): with pytest.raises(RuntimeError): - await make_raw_historical_request() + await make_raw_historical_request(opc=self.opc) if __name__ == "__main__": From f24468ff5818a8efa7dde894f484ac4d5b13a599 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Tue, 10 Sep 2024 10:57:46 +0200 Subject: [PATCH 16/35] Refactored DWH tests for better coverage. --- tests/dwh/dwh_test.py | 368 ++++++++++++++++++++---------------------- 1 file changed, 179 insertions(+), 189 deletions(-) diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index da186a6..583da8b 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -1,196 +1,186 @@ import pytest +import unittest import pyodbc import logging import datetime -from unittest.mock import Mock +from unittest.mock import Mock, patch, MagicMock, call from pyprediktormapclient.dwh.dwh import DWH -""" -Mock Functions -""" - -def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts( - connection_string, -): - raise pyodbc.DataError("Error code", "Error message") - - -def mock_pyodbc_connection_throws_error_tolerant_to_attempts( - connection_string, -): - def attempt_connect(): - if attempt_connect.counter < 3: - attempt_connect.counter += 1 - raise pyodbc.DatabaseError("Error code", "Temporary error message") - else: - raise pyodbc.DatabaseError("Error code", "Permanent error message") - - attempt_connect.counter = 0 - return attempt_connect() - - -""" -Helper Function -""" - - -def grs(): - """Generate a random string suitable for URL, database, username, - password.""" - return "test_string" - - -""" -Test Functions -""" - - -def test_init_when_instantiate_dwh_but_pyodbc_throws_error_with_tolerance_to_attempts_then_throw_exception( - monkeypatch, -): - driver_index = 0 - - # Mock the database connection - monkeypatch.setattr( - "pyprediktorutilities.dwh.dwh.pyodbc.connect", - mock_pyodbc_connection_throws_error_not_tolerant_to_attempts, - ) - - with pytest.raises(pyodbc.DataError): - DWH(grs(), grs(), grs(), grs(), driver_index) - - -def test_init_when_instantiate_dwh_but_pyodbc_throws_error_tolerant_to_attempts_then_retry_connecting_and_throw_exception( - caplog, monkeypatch -): - driver_index = 0 - - # Mock the database connection - monkeypatch.setattr( - "pyprediktorutilities.dwh.dwh.pyodbc.connect", - mock_pyodbc_connection_throws_error_tolerant_to_attempts, - ) - - with caplog.at_level(logging.ERROR): - with pytest.raises(pyodbc.DatabaseError): - DWH(grs(), grs(), grs(), grs(), driver_index) - - assert any( - "Failed to connect to the DataWarehouse after 3 attempts." in message - for message in caplog.messages - ) - - -def test_init_when_instantiate_dwh_but_driver_index_is_not_passed_then_instance_is_created( - monkeypatch, -): - # Mock the connection method to return a mock connection with a mock cursor - mock_cursor = Mock() - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2"]) - - dwh = DWH(grs(), grs(), grs(), grs()) - assert dwh is not None - assert dwh.driver == "Driver1" - - -""" -version -""" - - -def test_version_when_version_data_is_returned_then_return_version_data( - monkeypatch, -): - driver_index = 2 - data_returned_by_dwh = [ - ( - "2.3.1", - datetime.datetime(2023, 11, 14, 7, 5, 19, 830000), - "Updated Dwh from procs", - 2, - 3, - 1, - ) - ] - - expected_query = "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" - expected_result = { - "DWHVersion": "2.3.1", - "UpdateDate": datetime.datetime(2023, 11, 14, 7, 5, 19, 830000), - "Comment": "Updated Dwh from procs", - "MajorVersionNo": 2, - "MinorVersionNo": 3, - "InterimVersionNo": 1, - } - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = data_returned_by_dwh - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("DWHVersion", None), - ("UpdateDate", None), - ("Comment", None), - ("MajorVersionNo", None), - ("MinorVersionNo", None), - ("InterimVersionNo", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - dwh = DWH(grs(), grs(), grs(), grs(), driver_index) - version = dwh.version() - - mock_cursor.execute.assert_called_once_with(expected_query) - assert version == expected_result - - -def test_version_when_version_data_is_not_returned_then_return_empty_tuple( - monkeypatch, -): - driver_index = 2 - expected_query = "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" - expected_result = {} - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = [] - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("DWHVersion", None), - ("UpdateDate", None), - ("Comment", None), - ("MajorVersionNo", None), - ("MinorVersionNo", None), - ("InterimVersionNo", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - dwh = DWH(grs(), grs(), grs(), grs(), driver_index) - version = dwh.version() - - mock_cursor.execute.assert_called_once_with(expected_query) - assert version == expected_result +class TestCaseDWH: + + @pytest.fixture + def mock_pyodbc_connect(self): + with patch('pyodbc.connect') as mock_connect: + mock_connection = Mock() + mock_cursor = Mock() + mock_connection.cursor.return_value = mock_cursor + mock_connect.return_value = mock_connection + yield mock_connect + + @pytest.fixture + def dwh_instance(self, mock_pyodbc_connect): + with patch.object(DWH, '_DWH__initialize_context_services'): + return DWH("test_url", "test_db", "test_user", "test_pass", -1) + + @pytest.fixture + def mock_iter_modules(self): + with patch('pkgutil.iter_modules') as mock_iter_modules: + yield mock_iter_modules + + @patch.object(DWH, '_DWH__initialize_context_services') + @patch('pyprediktormapclient.dwh.dwh.Db.__init__') + def test_init(self, mock_db_init, mock_initialize_context_services, mock_pyodbc_connect): + DWH("test_url", "test_db", "test_user", "test_pass", -1) + mock_db_init.assert_called_once_with("test_url", "test_db", "test_user", "test_pass", -1) + mock_initialize_context_services.assert_called_once() + + @patch('pyprediktorutilities.dwh.dwh.pyodbc.connect') + def test_init_connection_error(self, mock_connect): + mock_connect.side_effect = pyodbc.DataError("Error code", "Error message") + with pytest.raises(pyodbc.DataError): + DWH("test_url", "test_db", "test_user", "test_pass", 0) + + @patch('pyprediktorutilities.dwh.dwh.pyodbc.connect') + def test_init_connection_retry(self, mock_connect, caplog): + mock_connect.side_effect = [ + pyodbc.DatabaseError("Error code", "Temporary error message"), + pyodbc.DatabaseError("Error code", "Temporary error message"), + pyodbc.DatabaseError("Error code", "Permanent error message") + ] + with caplog.at_level(logging.ERROR): + with pytest.raises(pyodbc.DatabaseError): + DWH("test_url", "test_db", "test_user", "test_pass", 0) + assert "Failed to connect to the DataWarehouse after 3 attempts." in caplog.text + + @patch('pyodbc.connect') + @patch('pyodbc.drivers') + def test_init_default_driver(self, mock_drivers, mock_connect): + mock_drivers.return_value = ["Driver1", "Driver2"] + mock_connect.return_value = Mock() + dwh = DWH("test_url", "test_db", "test_user", "test_pass") + assert dwh.driver == "Driver1" + + @patch.object(DWH, 'fetch') + def test_version_with_results(self, mock_fetch, dwh_instance): + expected_result = { + "DWHVersion": "2.3.1", + "UpdateDate": datetime.datetime(2023, 11, 14, 7, 5, 19, 830000), + "Comment": "Updated Dwh from procs", + "MajorVersionNo": 2, + "MinorVersionNo": 3, + "InterimVersionNo": 1, + } + mock_fetch.return_value = [expected_result] + version = dwh_instance.version() + assert version == expected_result + mock_fetch.assert_called_once_with("SET NOCOUNT ON; EXEC [dbo].[GetVersion]") + + @patch.object(DWH, 'fetch') + def test_version_without_results(self, mock_fetch, dwh_instance): + mock_fetch.return_value = [] + version = dwh_instance.version() + assert version == {} + mock_fetch.assert_called_once_with("SET NOCOUNT ON; EXEC [dbo].[GetVersion]") + + + @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') + def test_initialize_context_services(self, mock_import_module, mock_iter_modules, dwh_instance): + mock_iter_modules.return_value = [ + (None, 'pyprediktormapclient.dwh.context.enercast', False), + (None, 'pyprediktormapclient.dwh.context.plant', False), + (None, 'pyprediktormapclient.dwh.context.solcast', False) + ] + + class TestService: + def __init__(self, dwh): + self.dwh = dwh + + def mock_import(name): + mock_module = MagicMock() + mock_module.__dir__ = lambda *args: ['TestService'] + mock_module.TestService = TestService + return mock_module + + mock_import_module.side_effect = mock_import + + with patch.object(dwh_instance, '_is_attr_valid_service_class', return_value=True): + dwh_instance._DWH__initialize_context_services() + + expected_calls = [ + call('pyprediktormapclient.dwh.context.enercast'), + call('pyprediktormapclient.dwh.context.plant'), + call('pyprediktormapclient.dwh.context.solcast') + ] + assert all(expected_call in mock_import_module.call_args_list for expected_call in expected_calls), \ + "Not all expected module imports were made" + assert hasattr(dwh_instance, 'enercast') + assert hasattr(dwh_instance, 'plant') + assert hasattr(dwh_instance, 'solcast') + + for attr in ['enercast', 'plant', 'solcast']: + assert isinstance(getattr(dwh_instance, attr), TestService) + assert getattr(dwh_instance, attr).dwh == dwh_instance + + + @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') + def test_initialize_context_services_with_modules(self, mock_import_module, mock_iter_modules, dwh_instance): + mock_iter_modules.return_value = [ + (None, 'pyprediktormapclient.dwh.context.enercast', False), + (None, 'pyprediktormapclient.dwh.context.plant', False), + (None, 'pyprediktormapclient.dwh.context.solcast', False), + ] + + class TestService: + def __init__(self, dwh): + self.dwh = dwh + + def mock_import(name): + mock_module = MagicMock() + mock_module.__dir__ = lambda *args: ['TestService'] + mock_module.TestService = TestService + return mock_module + + mock_import_module.side_effect = mock_import + dwh_instance._DWH__initialize_context_services() + + expected_modules = ['pyprediktormapclient.dwh.context.enercast', + 'pyprediktormapclient.dwh.context.plant', + 'pyprediktormapclient.dwh.context.solcast'] + imported_modules = [call[0][0] for call in mock_import_module.call_args_list] + + assert all(module in imported_modules for module in expected_modules), "Not all expected modules were imported" + assert hasattr(dwh_instance, 'enercast'), "enercast attribute is missing" + assert hasattr(dwh_instance, 'plant'), "plant attribute is missing" + assert hasattr(dwh_instance, 'solcast'), "solcast attribute is missing" + + for attr in ['enercast', 'plant', 'solcast']: + assert isinstance(getattr(dwh_instance, attr), TestService), f"{attr} is not an instance of TestService" + assert getattr(dwh_instance, attr).dwh == dwh_instance, f"{attr}'s dwh is not the dwh_instance" + + + @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') + def test_initialize_context_services_with_package(self, mock_import_module, mock_iter_modules, dwh_instance): + mock_iter_modules.return_value = [ + (None, 'pyprediktormapclient.dwh.context.package', True), + ] + + dwh_instance._DWH__initialize_context_services() + + imported_modules = [call[0][0] for call in mock_import_module.call_args_list] + assert 'pyprediktormapclient.dwh.context.package' not in imported_modules, "Package was unexpectedly imported" + assert not hasattr(dwh_instance, 'package'), "package attribute unexpectedly exists" + + def test_is_attr_valid_service_class(self, dwh_instance): + class TestClass: + pass + + class IDWH: + pass + + assert dwh_instance._is_attr_valid_service_class(TestClass) is True + assert dwh_instance._is_attr_valid_service_class(IDWH) is True + assert dwh_instance._is_attr_valid_service_class('not_a_class') is False + +if __name__ == '__main__': + unittest.main() From b2d32211b9b39f10aa20e20a7e3c56cd769b79a9 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Tue, 10 Sep 2024 14:57:44 +0200 Subject: [PATCH 17/35] Refactored and optimized DB class tests. --- src/pyprediktormapclient/dwh/db.py | 28 +- tests/dwh/db_test.py | 1070 +++++++--------------------- 2 files changed, 267 insertions(+), 831 deletions(-) diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index c78d59a..ca23b34 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -161,31 +161,31 @@ def execute(self, query: str, *args, **kwargs) -> List[Any]: @validate_call def __set_driver(self, driver_index: int) -> None: - """Sets the driver to use for the connection to the database. + """Sets the driver for the database connection. Args: - driver (int): The index of the driver to use. If the index is -1 or + driver (int): Index of the driver in the list of available drivers. If the index is -1 or in general below 0, pyPrediktorMapClient is going to choose the driver for you. + + Raises: + ValueError: If no valid driver is found. """ + available_drivers = self.__get_list_of_available_and_supported_pyodbc_drivers() + + if not available_drivers: + raise ValueError("No supported ODBC drivers found.") + if driver_index < 0: - self.driver = ( - self.__get_list_of_available_and_supported_pyodbc_drivers()[0] - ) - return - - if self.__get_number_of_available_pyodbc_drivers() < ( - driver_index + 1 - ): + self.driver = available_drivers[0] + elif driver_index >= len(available_drivers): raise ValueError( f"Driver index {driver_index} is out of range. Please use " f"the __get_list_of_available_pyodbc_drivers() method " f"to list all available drivers." ) - - self.driver = self.__get_list_of_supported_pyodbc_drivers()[ - driver_index - ] + else: + self.driver = available_drivers[driver_index] @validate_call def __get_number_of_available_pyodbc_drivers(self) -> int: diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 657fd7d..520260e 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -4,823 +4,259 @@ import pyodbc import logging import pandas as pd -from unittest.mock import Mock +from unittest.mock import Mock, patch from pyprediktormapclient.dwh.db import Db from pandas.testing import assert_frame_equal -""" -Helpers -""" - - -class mock_pyodbc_connection: - def __init__(self, connection_string): - pass - - def cursor(self): - return - - -def mock_pyodbc_connection_throws_error_not_tolerant_to_attempts( - connection_string, -): - raise pyodbc.DataError("Error code", "Error message") - - -def mock_pyodbc_connection_throws_error_tolerant_to_attempts( - connection_string, -): - raise pyodbc.DatabaseError("Error code", "Error message") - - -def grs(): - """Generate a random string.""" - return "".join( - random.choices(string.ascii_uppercase + string.digits, k=10) - ) - - -""" -__init__ -""" - - -def test_init_when_instantiate_db_then_instance_is_created(monkeypatch): - driver_index = 0 - - # Mock the database connection - monkeypatch.setattr( - "pyprediktormapclient.dwh.db.pyodbc.connect", mock_pyodbc_connection - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - assert db is not None - - -def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( - monkeypatch, -): - driver_index = 0 - - # Mock the absence of ODBC drivers - monkeypatch.setattr( - "pyprediktormapclient.dwh.db.pyodbc.drivers", lambda: [] - ) - - with pytest.raises(ValueError) as excinfo: - Db(grs(), grs(), grs(), grs(), driver_index) - assert "Driver index 0 is out of range." in str(excinfo.value) - - -def test_init_when_instantiate_db_but_pyodbc_throws_error_with_tolerance_to_attempts_then_throw_exception( - monkeypatch, -): - driver_index = 0 - - # Mock the database connection - monkeypatch.setattr( - "pyprediktormapclient.dwh.db.pyodbc.connect", - mock_pyodbc_connection_throws_error_not_tolerant_to_attempts, - ) - - with pytest.raises(pyodbc.DataError): - Db(grs(), grs(), grs(), grs(), driver_index) - - -def test_init_when_instantiate_db_but_pyodbc_throws_error_tolerant_to_attempts_then_retry_connecting_and_throw_exception( - caplog, monkeypatch -): - driver_index = 0 - - # Mock the database connection - monkeypatch.setattr( - "pyprediktormapclient.dwh.db.pyodbc.connect", - mock_pyodbc_connection_throws_error_tolerant_to_attempts, - ) - - with caplog.at_level(logging.ERROR): - with pytest.raises(pyodbc.DatabaseError): - Db(grs(), grs(), grs(), grs(), driver_index) - - assert any( - "Failed to connect to the DataWarehouse after 3 attempts." in message - for message in caplog.messages - ) - - -def test_init_when_instantiate_dwh_but_driver_index_is_not_passed_then_instance_is_created( - monkeypatch, -): - # Mock the connection method to return a mock connection with a mock cursor - mock_cursor = Mock() - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr("pyodbc.drivers", lambda: ["Driver1", "Driver2"]) - - db = Db(grs(), grs(), grs(), grs()) - assert db is not None - assert db.driver == "Driver1" - - -""" -fetch -""" - - -def test_fetch_when_init_db_connection_is_successfull_but_fails_when_calling_fetch_then_throw_exception( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 0 - - # Mock the cursor - mock_cursor = Mock() - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection_success = Mock() - mock_connection_success.cursor.return_value = mock_cursor - - mock_connection_fail = Mock() - mock_connection_fail.cursor.side_effect = pyodbc.DataError( - "Error code", "Database data error" - ) - - monkeypatch.setattr( - "pyodbc.connect", - Mock(side_effect=[mock_connection_success, mock_connection_fail]), - ) - - with pytest.raises(pyodbc.DataError): - db = Db(grs(), grs(), grs(), grs(), driver_index) - db.connection = False - db.fetch(query) - - -def test_fetch_when_to_dataframe_is_false_and_no_data_is_returned_then_return_empty_list( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - - expected_result = [] - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = [] - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query) - - mock_cursor.execute.assert_called_once_with(query) - assert actual_result == expected_result - - -def test_fetch_when_to_dataframe_is_false_and_single_data_set_is_returned_then_return_list_representing_single_data_set( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - data_returned_by_db = [ - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "KL-MN", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - - expected_result = [ - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "KL-MN", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ] - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = data_returned_by_db - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query) - - mock_cursor.execute.assert_called_once_with(query) - assert actual_result == expected_result - - -def test_fetch_when_to_dataframe_is_false_and_multiple_data_sets_are_returned_then_return_list_of_lists_representing_multiple_data_sets( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - data_returned_by_db_set_one = [ - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "KL-MN", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - data_returned_by_db_set_two = [ - ( - "ALPHA", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "BETA", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - - expected_result = [ - [ - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "KL-MN", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ], - [ - { - "plantname": "ALPHA", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "BETA", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ], - ] - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.side_effect = [ - data_returned_by_db_set_one, - data_returned_by_db_set_two, - ] - mock_cursor.nextset.side_effect = [True, False] - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query) - - mock_cursor.execute.assert_called_once_with(query) - assert actual_result == expected_result - - -def test_fetch_when_to_dataframe_is_true_and_no_data_is_returned_then_return_empty_dataframe( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = [] - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query, True) - - mock_cursor.execute.assert_called_once_with(query) - assert actual_result.empty - - -def test_fetch_when_to_dataframe_is_true_and_single_data_set_is_returned_then_return_dataframe( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - data_returned_by_db = [ - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "KL-MN", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - - expected_result = [ - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "KL-MN", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ] - expected_df = pd.DataFrame(expected_result) - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.return_value = data_returned_by_db - mock_cursor.nextset.return_value = False - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query, True) - - mock_cursor.execute.assert_called_once_with(query) - assert_frame_equal( - actual_result.reset_index(drop=True), - expected_df.reset_index(drop=True), - check_dtype=False, - ) - - -def test_fetch_when_to_dataframe_is_true_and_multiple_data_sets_are_returned_then_return_list_of_dataframes_representing_multiple_data_sets( - monkeypatch, -): - query = "SELECT * FROM mytable" - driver_index = 2 - data_returned_by_db_set_one = [ - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "XY-ZK", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "KL-MN", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - data_returned_by_db_set_two = [ - ( - "ALPHA", - "1234-abcd-efgh-5678", - "SOME_KEY", - 13, - 168, - "pv_power_advanced", - "PT15M", - ), - ( - "BETA", - "1234-abcd-efgh-5678", - "SOME_KEY", - 14, - 168, - "pv_power_advanced", - "PT15M", - ), - ] - - expected_result_set_one = [ - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "XY-ZK", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "KL-MN", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ] - expected_result_set_two = [ - { - "plantname": "ALPHA", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 13, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - { - "plantname": "BETA", - "resource_id": "1234-abcd-efgh-5678", - "api_key": "SOME_KEY", - "ExtForecastTypeKey": 14, - "hours": 168, - "output_parameters": "pv_power_advanced", - "period": "PT15M", - }, - ] - expected_df_set_one = pd.DataFrame(expected_result_set_one) - expected_df_set_two = pd.DataFrame(expected_result_set_two) - - # Mock the cursor's fetchall methods - mock_cursor = Mock() - mock_cursor.fetchall.side_effect = [ - data_returned_by_db_set_one, - data_returned_by_db_set_two, - ] - mock_cursor.nextset.side_effect = [True, False] - mock_cursor.description = [ - ("plantname", None), - ("resource_id", None), - ("api_key", None), - ("ExtForecastTypeKey", None), - ("hours", None), - ("output_parameters", None), - ("period", None), - ] - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.fetch(query, True) - - mock_cursor.execute.assert_called_once_with(query) - assert_frame_equal( - actual_result[0].reset_index(drop=True), - expected_df_set_one, - check_dtype=False, - ) - assert_frame_equal( - actual_result[1].reset_index(drop=True), - expected_df_set_two, - check_dtype=False, - ) - - -""" -execute -""" - - -def test_execute_when_init_db_connection_is_successfull_but_fails_when_calling_execute_then_throw_exception( - monkeypatch, -): - query = "INSERT INTO mytable VALUES (1, 'test')" - driver_index = 0 - - # Mock the cursor - mock_cursor = Mock() - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection_success = Mock() - mock_connection_success.cursor.return_value = mock_cursor - - mock_connection_fail = Mock() - mock_connection_fail.cursor.side_effect = pyodbc.DataError( - "Error code", "Database data error" - ) - - monkeypatch.setattr( - "pyodbc.connect", - Mock(side_effect=[mock_connection_success, mock_connection_fail]), - ) - - with pytest.raises(pyodbc.DataError): - db = Db(grs(), grs(), grs(), grs(), driver_index) - db.connection = False - db.execute(query) - - -def test_execute_when_parameter_passed_then_fetch_results_and_return_data( - monkeypatch, -): - query = "INSERT INTO mytable VALUES (?, ?)" - param_one = "John" - param_two = "Smith" - driver_index = 0 - expected_result = [{"id": 13}] - - # Mock the cursor and execute - mock_cursor = Mock() - mock_execute = Mock() - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - - monkeypatch.setattr( - "pyodbc.connect", - Mock(return_value=mock_connection), - ) - - # Mock the fetch method - mock_fetch = Mock(return_value=expected_result) - mock_cursor.execute = mock_execute - mock_cursor.fetchall = mock_fetch - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.execute(query, param_one, param_two) - - mock_execute.assert_called_once_with(query, param_one, param_two) - mock_fetch.assert_called_once() - assert actual_result == expected_result - - -def test_execute_when_fetchall_throws_error_then_return_empty_list( - monkeypatch, -): - query = "INSERT INTO mytable VALUES (?, ?)" - param_one = "John" - param_two = "Smith" - driver_index = 0 - - # Mock the cursor and execute - mock_cursor = Mock() - mock_execute = Mock() - mock_fetchall = Mock(side_effect=Exception("Error occurred")) - - # Mock the connection method to return a mock connection with a mock cursor - mock_connection = Mock() - mock_connection.cursor.return_value = mock_cursor - - monkeypatch.setattr( - "pyodbc.connect", - Mock(return_value=mock_connection), - ) - - # Mock the fetchall method - mock_cursor.execute = mock_execute - mock_cursor.fetchall = mock_fetchall - - db = Db(grs(), grs(), grs(), grs(), driver_index) - actual_result = db.execute(query, param_one, param_two) - - mock_execute.assert_called_once_with(query, param_one, param_two) - mock_fetchall.assert_called_once() - assert actual_result == [] +class TestCaseDB: + @staticmethod + def grs(): + """Generate a random string.""" + return ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) + + @pytest.fixture + def mock_pyodbc_connect(self, monkeypatch): + mock_connection = Mock() + mock_cursor = Mock() + mock_connection.cursor.return_value = mock_cursor + monkeypatch.setattr('pyodbc.connect', lambda *args, **kwargs: mock_connection) + return mock_cursor + + @pytest.fixture + def mock_pyodbc_drivers(self, monkeypatch): + monkeypatch.setattr('pyodbc.drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) + + @pytest.fixture + def mock_get_drivers(self, monkeypatch): + monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: ['Driver1']) + + @pytest.fixture + def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): + return Db(self.grs(), self.grs(), self.grs(), self.grs()) + + def test_init_successful(self, db_instance): + assert db_instance is not None + assert db_instance.driver == 'Driver1' + + def test_init_no_drivers(self, monkeypatch): + monkeypatch.setattr('pyodbc.drivers', lambda: []) + monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: []) + + with pytest.raises(ValueError, match="No supported ODBC drivers found."): + Db(self.grs(), self.grs(), self.grs(), self.grs()) + + @pytest.mark.parametrize('error, expected_error, expected_message', [ + (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'Data Error Error code: Error message'), + (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.DatabaseError, 'Failed to connect to the DataWarehouse after 3 attempts.'), + ]) + def test_init_connection_error(self, monkeypatch, error, expected_error, expected_message, caplog, mock_get_drivers): + monkeypatch.setattr('pyodbc.connect', Mock(side_effect=error)) + with pytest.raises(expected_error): + with caplog.at_level(logging.ERROR): + Db(self.grs(), self.grs(), self.grs(), self.grs()) + assert expected_message in caplog.text + + def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( + self, monkeypatch + ): + driver_index = 0 + monkeypatch.setattr('pyodbc.drivers', lambda: ['DRIVER1']) + monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: []) + + with pytest.raises(ValueError, match="No supported ODBC drivers found."): + Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + + def test_init_with_out_of_range_driver_index(self, monkeypatch): + driver_index = 1 + monkeypatch.setattr('pyodbc.drivers', lambda: ['DRIVER1']) + monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: ['DRIVER1']) + + with pytest.raises(ValueError, match="Driver index 1 is out of range."): + Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + + def test_fetch_connection_error(self, db_instance, monkeypatch): + monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.DataError('Error', 'Connection Error'))) + with pytest.raises(pyodbc.DataError): + db_instance.fetch("SELECT * FROM test_table") + + @pytest.mark.parametrize('to_dataframe, expected_result', [ + (False, []), + (True, pd.DataFrame()), + ]) + def test_fetch_no_data(self, db_instance, mock_pyodbc_connect, to_dataframe, expected_result): + mock_pyodbc_connect.fetchall.return_value = [] + mock_pyodbc_connect.nextset.return_value = False + mock_pyodbc_connect.description = [("column1", None), ("column2", None)] + + result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) + + if to_dataframe: + assert result.empty + else: + assert result == expected_result + + @pytest.mark.parametrize('to_dataframe', [False, True]) + def test_fetch_single_dataset(self, db_instance, mock_pyodbc_connect, to_dataframe): + data = [("value1", 1), ("value2", 2)] + mock_pyodbc_connect.fetchall.return_value = data + mock_pyodbc_connect.nextset.return_value = False + mock_pyodbc_connect.description = [("column1", None), ("column2", None)] + + result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) + + if to_dataframe: + expected = pd.DataFrame(data, columns=["column1", "column2"]) + assert_frame_equal(result, expected) + else: + expected = [{"column1": "value1", "column2": 1}, {"column1": "value2", "column2": 2}] + assert result == expected + + @pytest.mark.parametrize('to_dataframe', [False, True]) + def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_dataframe): + data1 = [("value1", 1), ("value2", 2)] + data2 = [("value3", 3), ("value4", 4)] + mock_pyodbc_connect.fetchall.side_effect = [data1, data2] + mock_pyodbc_connect.nextset.side_effect = [True, False] + mock_pyodbc_connect.description = [("column1", None), ("column2", None)] + + result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) + + if to_dataframe: + expected1 = pd.DataFrame(data1, columns=["column1", "column2"]) + expected2 = pd.DataFrame(data2, columns=["column1", "column2"]) + assert len(result) == 2 + assert_frame_equal(result[0], expected1) + assert_frame_equal(result[1], expected2) + else: + expected = [ + [{"column1": "value1", "column2": 1}, {"column1": "value2", "column2": 2}], + [{"column1": "value3", "column2": 3}, {"column1": "value4", "column2": 4}] + ] + assert result == expected + + def test_execute_connection_error(self, db_instance, monkeypatch): + monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.Error('Error', 'Connection Error'))) + with pytest.raises(pyodbc.Error): + db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") + + def test_execute_with_parameters(self, db_instance, mock_pyodbc_connect): + query = "INSERT INTO test_table VALUES (?, ?)" + params = ("John", "Smith") + expected_result = [{"id": 13}] + mock_pyodbc_connect.fetchall.return_value = expected_result + + result = db_instance.execute(query, *params) + + mock_pyodbc_connect.execute.assert_called_once_with(query, *params) + mock_pyodbc_connect.fetchall.assert_called_once() + assert result == expected_result + + def test_execute_fetchall_error(self, db_instance, mock_pyodbc_connect): + query = "INSERT INTO test_table VALUES (?, ?)" + params = ("John", "Smith") + mock_pyodbc_connect.fetchall.side_effect = Exception("Error occurred") + + result = db_instance.execute(query, *params) + + mock_pyodbc_connect.execute.assert_called_once_with(query, *params) + mock_pyodbc_connect.fetchall.assert_called_once() + assert result == [] + + def test_context_manager_enter(self, db_instance): + assert db_instance.__enter__() == db_instance + + def test_context_manager_exit(self, db_instance, monkeypatch): + disconnect_called = False + def mock_disconnect(): + nonlocal disconnect_called + disconnect_called = True + + monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) + db_instance.connection = True + + db_instance.__exit__(None, None, None) + assert disconnect_called + + def test_set_driver_with_valid_index(self, monkeypatch, db_instance): + available_drivers = ['DRIVER1', 'DRIVER2'] + monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: available_drivers) + + db_instance._Db__set_driver(1) + assert db_instance.driver == 'DRIVER2' + + def test_get_number_of_available_pyodbc_drivers(self, db_instance, monkeypatch): + monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: ['DRIVER1', 'DRIVER2']) + assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 2 + + def test_connect_success(self, db_instance, monkeypatch): + connect_called = False + def mock_connect(*args, **kwargs): + nonlocal connect_called + connect_called = True + return Mock(cursor=Mock()) + + with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + db_instance._Db__connect() + assert mock.called + assert connect_called + + def test_connect_retry_on_operational_error(self, db_instance, monkeypatch): + attempt_count = 0 + def mock_connect(*args, **kwargs): + nonlocal attempt_count + attempt_count += 1 + if attempt_count < 3: + raise pyodbc.OperationalError('Test error') + return Mock(cursor=Mock()) + + with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + db_instance._Db__connect() + assert mock.call_count == 3 + + def test_connect_raise_on_integrity_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.IntegrityError('Test integrity error') + + with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.IntegrityError): + db_instance._Db__connect() + + def test_connect_raise_on_programming_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.ProgrammingError('Test programming error') + + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.ProgrammingError): + db_instance._Db__connect() + + def test_connect_raise_on_not_supported_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.NotSupportedError('Test not supported error') + + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.NotSupportedError): + db_instance._Db__connect() + + def test_connect_retry_on_generic_error(self, db_instance, monkeypatch): + attempt_count = 0 + def mock_connect(*args, **kwargs): + nonlocal attempt_count + attempt_count += 1 + if attempt_count < 3: + raise pyodbc.Error('Test generic error') + return Mock(cursor=Mock()) + + with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + db_instance._Db__connect() + assert mock.call_count == 3 + + def test_disconnect(self, db_instance): + mock_connection = Mock() + db_instance.connection = mock_connection + db_instance.cursor = Mock() + + db_instance._Db__disconnect() + + assert mock_connection.close.called + assert db_instance.connection is None + assert db_instance.cursor is None \ No newline at end of file From 97cbff926eb74d6289b227862299ab4662ddb998 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Wed, 11 Sep 2024 14:57:48 +0200 Subject: [PATCH 18/35] Refactored and expanded test coverage for Db class. --- src/pyprediktormapclient/dwh/db.py | 52 ++++---- tests/dwh/db_test.py | 196 +++++++++++++++++++++++++---- 2 files changed, 194 insertions(+), 54 deletions(-) diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index ca23b34..46cf3c1 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -193,25 +193,32 @@ def __get_number_of_available_pyodbc_drivers(self) -> int: @validate_call def __get_list_of_supported_pyodbc_drivers(self) -> List[Any]: - return pyodbc.drivers() + try: + return pyodbc.drivers() + except pyodbc.Error as err: + logger.error(f"Error retrieving drivers: {err}") + return [] @validate_call def __get_list_of_available_and_supported_pyodbc_drivers( - self, + self ) -> List[Any]: available_drivers = [] - for driver in self.__get_list_of_supported_pyodbc_drivers(): + supported_drivers = self.__get_list_of_supported_pyodbc_drivers() + + for driver in supported_drivers: try: - pyodbc.connect( + connection_string = ( f"UID={self.username};" + f"PWD={self.password};" + f"DRIVER={driver};" + f"SERVER={self.url};" - + f"DATABASE={self.database};", - timeout=3, + + f"DATABASE={self.database};" ) + pyodbc.connect(connection_string, timeout=3) available_drivers.append(driver) - except pyodbc.Error: + except pyodbc.Error as err: + logger.info(f"Driver {driver} could not connect: {err}") pass return available_drivers @@ -234,29 +241,24 @@ def __connect(self) -> None: self.connection = pyodbc.connect(self.connection_string) self.cursor = self.connection.cursor() logging.info("Connection successfull!") - break + return # Exceptions once thrown there is no point attempting - except pyodbc.DataError as err: - logger.error(f"Data Error {err.args[0]}: {err.args[1]}") - raise - except pyodbc.IntegrityError as err: - logger.error(f"Integrity Error {err.args[0]}: {err.args[1]}") - raise except pyodbc.ProgrammingError as err: - logger.error(f"Programming Error {err.args[0]}: {err.args[1]}") + logger.error(f"Programming Error {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") logger.warning( "There seems to be a problem with your code. Please " "check your code and try again." ) raise - except pyodbc.NotSupportedError as err: - logger.error(f"Not supported {err.args[0]}: {err.args[1]}") + + except (pyodbc.DataError, pyodbc.IntegrityError, pyodbc.NotSupportedError) as err: + logger.error(f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") raise # Exceptions when thrown we can continue attempting except pyodbc.OperationalError as err: - logger.error(f"Operational Error {err.args[0]}: {err.args[1]}") + logger.error(f"Operational Error: {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") logger.warning( "Pyodbc is having issues with the connection. This " "could be due to the wrong driver being used. Please " @@ -264,22 +266,18 @@ def __connect(self) -> None: "the __get_list_of_available_and_supported_pyodbc_drivers() method " "and try again." ) - attempt += 1 if self.__are_connection_attempts_reached(attempt): raise - except pyodbc.DatabaseError as err: - logger.error(f"Database Error {err.args[0]}: {err.args[1]}") + except (pyodbc.DatabaseError, pyodbc.Error) as err: + logger.error(f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") attempt += 1 if self.__are_connection_attempts_reached(attempt): - raise - except pyodbc.Error as err: - logger.error(f"Generic Error {err.args[0]}: {err.args[1]}") + break - attempt += 1 - if self.__are_connection_attempts_reached(attempt): - raise + if not self.connection: + raise pyodbc.Error("Failed to connect to the database") @validate_call def __are_connection_attempts_reached(self, attempt) -> bool: diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 520260e..f752f09 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -4,7 +4,7 @@ import pyodbc import logging import pandas as pd -from unittest.mock import Mock, patch +from unittest.mock import Mock, patch, MagicMock from pyprediktormapclient.dwh.db import Db from pandas.testing import assert_frame_equal @@ -45,16 +45,24 @@ def test_init_no_drivers(self, monkeypatch): with pytest.raises(ValueError, match="No supported ODBC drivers found."): Db(self.grs(), self.grs(), self.grs(), self.grs()) - @pytest.mark.parametrize('error, expected_error, expected_message', [ - (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'Data Error Error code: Error message'), - (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.DatabaseError, 'Failed to connect to the DataWarehouse after 3 attempts.'), + @pytest.mark.parametrize('error, expected_error, expected_log_message', [ + (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'DataError Error code: Error message'), + (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.Error, 'DatabaseError Error code: Error message'), ]) - def test_init_connection_error(self, monkeypatch, error, expected_error, expected_message, caplog, mock_get_drivers): + def test_init_connection_error(self, monkeypatch, error, expected_error, expected_log_message, caplog, mock_get_drivers): monkeypatch.setattr('pyodbc.connect', Mock(side_effect=error)) - with pytest.raises(expected_error): + with pytest.raises(expected_error) as exc_info: with caplog.at_level(logging.ERROR): Db(self.grs(), self.grs(), self.grs(), self.grs()) - assert expected_message in caplog.text + + if expected_error == pyodbc.Error: + assert str(exc_info.value) == "Failed to connect to the database" + else: + assert str(exc_info.value) == str(error) + + assert expected_log_message in caplog.text + if expected_error == pyodbc.Error: + assert "Failed to connect to the DataWarehouse after 3 attempts" in caplog.text def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( self, monkeypatch @@ -74,6 +82,17 @@ def test_init_with_out_of_range_driver_index(self, monkeypatch): with pytest.raises(ValueError, match="Driver index 1 is out of range."): Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + def test_exit_with_connection(self, db_instance): + mock_connection = Mock() + db_instance.connection = mock_connection + db_instance.__exit__(None, None, None) + mock_connection.close.assert_called_once() + assert db_instance.connection is None + + def test_exit_without_connection(self, db_instance): + db_instance.connection = None + db_instance.__exit__(None, None, None) + def test_fetch_connection_error(self, db_instance, monkeypatch): monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.DataError('Error', 'Connection Error'))) with pytest.raises(pyodbc.DataError): @@ -188,67 +207,184 @@ def test_get_number_of_available_pyodbc_drivers(self, db_instance, monkeypatch): monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: ['DRIVER1', 'DRIVER2']) assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 2 + @patch('pyodbc.connect') + def test_get_available_and_supported_drivers(self, mock_connect, db_instance): + db_instance.__get_list_of_supported_pyodbc_drivers = Mock(return_value=['Driver1', 'Driver2', 'Driver3']) + mock_connect.side_effect = [None, pyodbc.Error, None] + + result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() + + assert result == ['Driver1', 'Driver3'] + assert mock_connect.call_count == 3 + + def test_get_list_of_available_and_supported_pyodbc_drivers_silently_passes_on_error(self, db_instance, monkeypatch): + mock_error = pyodbc.Error("Mock Error") + monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=mock_error)) + + result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() + assert result == [], "Should return an empty list when pyodbc.Error occurs" + def test_connect_success(self, db_instance, monkeypatch): connect_called = False + def mock_connect(*args, **kwargs): nonlocal connect_called connect_called = True return Mock(cursor=Mock()) - with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect) as mock: db_instance._Db__connect() assert mock.called assert connect_called - def test_connect_retry_on_operational_error(self, db_instance, monkeypatch): + def test_exit_disconnects_when_connection_exists(self, db_instance, monkeypatch): + disconnect_called = False + + def mock_disconnect(): + nonlocal disconnect_called + disconnect_called = True + + monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) + db_instance.connection = True + + db_instance.__exit__(None, None, None) + assert disconnect_called, "__disconnect should be called when __exit__ is invoked with an active connection" + + def test_connect_raises_programming_error_with_logging(self, db_instance, monkeypatch, caplog): + def mock_connect(*args, **kwargs): + raise pyodbc.ProgrammingError("some_code", "some_message") + + monkeypatch.setattr(pyodbc, 'connect', mock_connect) + + db_instance.connection = None + with pytest.raises(pyodbc.ProgrammingError): + db_instance._Db__connect() + + assert "Programming Error some_code: some_message" in caplog.text, "Programming error should be logged" + assert "There seems to be a problem with your code" in caplog.text, "Warning for ProgrammingError should be logged" + + def test_connect_raise_on_data_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.DataError(("DataError code", "Test data error")) + + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.DataError): + db_instance._Db__connect() + + def test_connect_raise_on_integrity_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.IntegrityError(("IntegrityError code", "Test integrity error")) + + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.IntegrityError): + db_instance._Db__connect() + + def test_connect_raise_on_not_supported_error(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.NotSupportedError(("NotSupportedError code", "Test not supported error")) + + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.NotSupportedError): + db_instance._Db__connect() + + def test_connect_attempts_three_times_on_operational_error(self, db_instance, monkeypatch, caplog): attempt_count = 0 + def mock_connect(*args, **kwargs): nonlocal attempt_count attempt_count += 1 if attempt_count < 3: - raise pyodbc.OperationalError('Test error') - return Mock(cursor=Mock()) - - with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + exc = pyodbc.OperationalError() + exc.args = ("OperationalError code", "Mock Operational Error") + raise exc + else: + return Mock() + + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect) as mock: db_instance._Db__connect() + assert "Operational Error: OperationalError code: Mock Operational Error" in caplog.text + assert "Pyodbc is having issues with the connection" in caplog.text assert mock.call_count == 3 - def test_connect_raise_on_integrity_error(self, db_instance, monkeypatch): + def test_connect_raises_after_max_attempts_on_operational_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.IntegrityError('Test integrity error') + raise pyodbc.OperationalError(("OperationalError code", "Test operational error")) - with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.IntegrityError): + db_instance.connection = None + db_instance.connection_attempts = 3 + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.OperationalError): db_instance._Db__connect() - def test_connect_raise_on_programming_error(self, db_instance, monkeypatch): + def test_connect_logs_database_error_and_retries(self, db_instance, monkeypatch, caplog): + attempt_count = 0 + def mock_connect(*args, **kwargs): - raise pyodbc.ProgrammingError('Test programming error') + nonlocal attempt_count + attempt_count += 1 + if attempt_count < 3: + raise pyodbc.DatabaseError(("DatabaseError code", "Test database error")) + return Mock(cursor=Mock()) + db_instance.connection = None with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.ProgrammingError): - db_instance._Db__connect() + db_instance._Db__connect() - def test_connect_raise_on_not_supported_error(self, db_instance, monkeypatch): + assert "DatabaseError ('DatabaseError code', 'Test database error'): No message" in caplog.text + assert attempt_count == 3 + + def test_connect_breaks_after_max_attempts_on_database_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.NotSupportedError('Test not supported error') + raise pyodbc.DatabaseError(("DatabaseError code", "Test database error")) + db_instance.connection = None + db_instance.connection_attempts = 3 with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.NotSupportedError): + with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): db_instance._Db__connect() def test_connect_retry_on_generic_error(self, db_instance, monkeypatch): attempt_count = 0 + def mock_connect(*args, **kwargs): nonlocal attempt_count attempt_count += 1 if attempt_count < 3: - raise pyodbc.Error('Test generic error') + raise pyodbc.Error(("Error code", "Test generic error")) return Mock(cursor=Mock()) - with patch('pyprediktormapclient.dwh.db.pyodbc.connect', side_effect=mock_connect) as mock: + db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect) as mock: db_instance._Db__connect() assert mock.call_count == 3 + def test_connect_raises_error_after_max_attempts(self, db_instance, monkeypatch): + def mock_connect(*args, **kwargs): + raise pyodbc.Error(("Error code", "Test error")) + + db_instance.connection = None + db_instance.connection_attempts = 3 + with patch('pyodbc.connect', side_effect=mock_connect): + with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + db_instance._Db__connect() + + def test_connect_exits_early_if_connection_exists(self, db_instance, monkeypatch): + connect_called = False + + def mock_connect(*args, **kwargs): + nonlocal connect_called + connect_called = True + return Mock() + + db_instance.connection = Mock() + with patch('pyodbc.connect', side_effect=mock_connect): + db_instance._Db__connect() + + assert not connect_called, "pyodbc.connect should not be called if connection already exists" def test_disconnect(self, db_instance): mock_connection = Mock() @@ -259,4 +395,10 @@ def test_disconnect(self, db_instance): assert mock_connection.close.called assert db_instance.connection is None - assert db_instance.cursor is None \ No newline at end of file + assert db_instance.cursor is None + + def test_disconnect_without_connection(self, db_instance): + db_instance.connection = None + db_instance.cursor = None + + db_instance._Db__disconnect() \ No newline at end of file From 9682de868d3dba461c4a31e79fc7c6b8891e6cfc Mon Sep 17 00:00:00 2001 From: MeenBna Date: Thu, 12 Sep 2024 15:56:28 +0200 Subject: [PATCH 19/35] Refactored Db class tests for better compatibility. --- src/pyprediktormapclient/dwh/db.py | 22 +- tests/dwh/db_test.py | 360 +++++++++++++++++++++++++---- tests/dwh/dwh_test.py | 39 +++- 3 files changed, 361 insertions(+), 60 deletions(-) diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index 46cf3c1..027e596 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -101,17 +101,13 @@ def fetch(self, query: str, to_dataframe: bool = False) -> List[Any]: data_sets = [] while True: - data_set = [] - columns = [col[0] for col in self.cursor.description] - for row in self.cursor.fetchall(): - data_set.append( - {name: row[index] for index, name in enumerate(columns)} - ) + data_set = [dict(zip(columns, row)) for row in self.cursor.fetchall()] - data_sets.append( - pd.DataFrame(data_set) if to_dataframe else data_set - ) + if to_dataframe: + data_sets.append(pd.DataFrame(data_set, columns=columns)) + else: + data_sets.append(data_set) if not self.cursor.nextset(): break @@ -143,16 +139,14 @@ def execute(self, query: str, *args, **kwargs) -> List[Any]: List[Any]: The results of the query. """ self.__connect() - self.cursor.execute(query, *args, **kwargs) - - result = [] try: + self.cursor.execute(query, *args, **kwargs) result = self.cursor.fetchall() except Exception as e: - logging.error(f"Failed to fetch results: {e}") + logging.error(f"Failed to execute query: {e}") + return [] self.__commit() - return result """ diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index f752f09..5395c80 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -2,10 +2,13 @@ import random import string import pyodbc +import inspect import logging import pandas as pd from unittest.mock import Mock, patch, MagicMock +from typing import Dict, List, Any, get_origin, get_args from pyprediktormapclient.dwh.db import Db +from pyprediktormapclient.dwh.idwh import IDWH from pandas.testing import assert_frame_equal class TestCaseDB: @@ -36,7 +39,10 @@ def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): def test_init_successful(self, db_instance): assert db_instance is not None - assert db_instance.driver == 'Driver1' + assert isinstance(db_instance.url, str) + assert isinstance(db_instance.database, str) + assert isinstance(db_instance.username, str) + assert isinstance(db_instance.driver, str) def test_init_no_drivers(self, monkeypatch): monkeypatch.setattr('pyodbc.drivers', lambda: []) @@ -82,6 +88,27 @@ def test_init_with_out_of_range_driver_index(self, monkeypatch): with pytest.raises(ValueError, match="Driver index 1 is out of range."): Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + def test_context_manager(self, db_instance): + with patch.object(db_instance, '_Db__disconnect') as mock_disconnect: + with db_instance: + pass + mock_disconnect.assert_called_once() + + def test_context_manager_enter(self, db_instance): + assert db_instance.__enter__() == db_instance + + def test_context_manager_exit(self, db_instance, monkeypatch): + disconnect_called = False + def mock_disconnect(): + nonlocal disconnect_called + disconnect_called = True + + monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) + db_instance.connection = True + + db_instance.__exit__(None, None, None) + assert disconnect_called + def test_exit_with_connection(self, db_instance): mock_connection = Mock() db_instance.connection = mock_connection @@ -93,11 +120,149 @@ def test_exit_without_connection(self, db_instance): db_instance.connection = None db_instance.__exit__(None, None, None) + def test_exit_disconnects_when_connection_exists(self, db_instance, monkeypatch): + disconnect_called = False + + def mock_disconnect(): + nonlocal disconnect_called + disconnect_called = True + + monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) + db_instance.connection = True + + db_instance.__exit__(None, None, None) + assert disconnect_called, "__disconnect should be called when __exit__ is invoked with an active connection" + + def test_connect_exits_early_if_connection_exists(self, db_instance, monkeypatch): + connect_called = False + + def mock_connect(*args, **kwargs): + nonlocal connect_called + connect_called = True + return Mock() + + db_instance.connection = Mock() + with patch('pyodbc.connect', side_effect=mock_connect): + db_instance._Db__connect() + + assert not connect_called, "pyodbc.connect should not be called if connection already exists" + + def test_exit_with_open_connection_and_cleanup(self, db_instance): + mock_connection = Mock() + db_instance.connection = mock_connection + db_instance.__exit__(None, None, None) + + mock_connection.close.assert_called_once() + assert db_instance.connection is None, "Connection should be set to None after exit" + + def test_idwh_abstract_methods(self): + assert inspect.isabstract(IDWH) + assert set(IDWH.__abstractmethods__) == {'version', 'fetch', 'execute'} + + def test_db_implements_idwh(self, db_instance, mock_pyodbc_connect): + def compare_signatures(impl_method, abstract_method): + if hasattr(impl_method, '__wrapped__'): + impl_method = impl_method.__wrapped__ + + impl_sig = inspect.signature(impl_method) + abstract_sig = inspect.signature(abstract_method) + + impl_return = impl_sig.return_annotation + abstract_return = abstract_sig.return_annotation + + impl_origin = get_origin(impl_return) or impl_return + abstract_origin = get_origin(abstract_return) or abstract_origin + + assert impl_origin == abstract_origin, f"Return type mismatch: {impl_return} is not compatible with {abstract_return}" + + if impl_origin is List: + impl_args = get_args(impl_return) + abstract_args = get_args(abstract_return) + + if not abstract_args: + abstract_args = (Any,) + + assert len(impl_args) == len(abstract_args), f"Generic argument count mismatch: {impl_args} vs {abstract_args}" + for impl_arg, abstract_arg in zip(impl_args, abstract_args): + assert impl_arg == abstract_arg or abstract_arg == Any, f"Generic argument mismatch: {impl_arg} is not compatible with {abstract_arg}" + + impl_params = list(impl_sig.parameters.values())[1:] + abstract_params = list(abstract_sig.parameters.values())[1:] + + assert len(impl_params) == len(abstract_params), f"Parameter count mismatch: implementation has {len(impl_params)}, abstract has {len(abstract_params)}" + + for impl_param, abstract_param in zip(impl_params, abstract_params): + assert impl_param.name == abstract_param.name, f"Parameter name mismatch: {impl_param.name} != {abstract_param.name}" + assert impl_param.annotation == abstract_param.annotation, f"Parameter type mismatch for {impl_param.name}: {impl_param.annotation} != {abstract_param.annotation}" + assert impl_param.default == abstract_param.default, f"Parameter default value mismatch for {impl_param.name}: {impl_param.default} != {abstract_param.default}" + + for method_name in ['fetch', 'execute']: + assert hasattr(db_instance, method_name), f"Db class is missing method: {method_name}" + assert callable(getattr(db_instance, method_name)), f"Db.{method_name} is not callable" + compare_signatures(getattr(db_instance, method_name), getattr(IDWH, method_name)) + + mock_pyodbc_connect.description = [('column1',), ('column2',)] + mock_pyodbc_connect.fetchall.return_value = [(1, 'a'), (2, 'b')] + mock_pyodbc_connect.nextset.return_value = False + + fetch_result = db_instance.fetch("SELECT * FROM dummy_table") + assert isinstance(fetch_result, list), "fetch method should return a list" + execute_result = db_instance.execute("INSERT INTO dummy_table VALUES (1, 'test')") + assert isinstance(execute_result, list), "execute method should return a list" + + @pytest.mark.parametrize('to_dataframe', [True, False]) + def test_fetch(self, db_instance, to_dataframe): + mock_cursor = Mock() + mock_cursor.description = [('col1',), ('col2',)] + mock_cursor.fetchall.return_value = [(1, 'a'), (2, 'b')] + mock_cursor.nextset.return_value = False + db_instance.cursor = mock_cursor + + result = db_instance.fetch('SELECT * FROM test', to_dataframe) + + if to_dataframe: + assert isinstance(result, pd.DataFrame) + assert result.to_dict('records') == [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}] + else: + assert result == [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}] + def test_fetch_connection_error(self, db_instance, monkeypatch): monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.DataError('Error', 'Connection Error'))) with pytest.raises(pyodbc.DataError): db_instance.fetch("SELECT * FROM test_table") + @pytest.mark.parametrize('to_dataframe', [False, True]) + def test_fetch_multiple_result_sets(self, db_instance, mock_pyodbc_connect, to_dataframe): + data1 = [(1, 'a'), (2, 'b')] + data2 = [(3, 'c'), (4, 'd')] + mock_pyodbc_connect.fetchall.side_effect = [data1, data2] + mock_pyodbc_connect.nextset.side_effect = [True, False] + mock_pyodbc_connect.description = [('col1',), ('col2',)] + + result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) + + if to_dataframe: + expected1 = pd.DataFrame(data1, columns=["col1", "col2"]) + expected2 = pd.DataFrame(data2, columns=["col1", "col2"]) + assert len(result) == 2 + assert_frame_equal(result[0], expected1) + assert_frame_equal(result[1], expected2) + else: + expected = [ + [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}], + [{'col1': 3, 'col2': 'c'}, {'col1': 4, 'col2': 'd'}] + ] + assert result == expected + + def test_fetch_no_columns(self, db_instance, mock_pyodbc_connect): + mock_pyodbc_connect.description = [] + mock_pyodbc_connect.fetchall.return_value = [] + mock_pyodbc_connect.nextset.return_value = False + + result = db_instance.fetch("SELECT * FROM empty_table") + + assert result == [] + @pytest.mark.parametrize('to_dataframe, expected_result', [ (False, []), (True, pd.DataFrame()), @@ -153,6 +318,26 @@ def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_data ] assert result == expected + def test_execute(self, db_instance): + mock_cursor = Mock() + mock_cursor.fetchall.return_value = [(1,)] + db_instance.cursor = mock_cursor + db_instance.connection = Mock() + + result = db_instance.execute('INSERT INTO test VALUES (?)', 'value') + + mock_cursor.execute.assert_called_with('INSERT INTO test VALUES (?)', 'value') + db_instance.connection.commit.assert_called_once() + assert result == [(1,)] + + def test_execute_sql_syntax_error(self, db_instance, mock_pyodbc_connect): + mock_pyodbc_connect.execute.side_effect = pyodbc.ProgrammingError("Syntax error") + + result = db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") + + assert result == [] + mock_pyodbc_connect.execute.assert_called_once_with("INSERT INTO test_table VALUES (1, 'test')") + def test_execute_connection_error(self, db_instance, monkeypatch): monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.Error('Error', 'Connection Error'))) with pytest.raises(pyodbc.Error): @@ -181,20 +366,51 @@ def test_execute_fetchall_error(self, db_instance, mock_pyodbc_connect): mock_pyodbc_connect.fetchall.assert_called_once() assert result == [] - def test_context_manager_enter(self, db_instance): - assert db_instance.__enter__() == db_instance + @pytest.mark.parametrize('query, args, kwargs', [ + ("INSERT INTO test_table VALUES (?, ?)", ('value1', 'value2'), {}), + ("UPDATE test_table SET column1 = ?", ('new_value',), {}), + ("DELETE FROM test_table WHERE id = ?", (1,), {}), + ("EXEC stored_procedure @param1=?, @param2=?", (), {'param1': 'value1', 'param2': 'value2'}), + ]) + def test_execute_with_various_queries(self, db_instance, mock_pyodbc_connect, query, args, kwargs): + expected_result = [{'id': 1}] + mock_pyodbc_connect.fetchall.return_value = expected_result - def test_context_manager_exit(self, db_instance, monkeypatch): - disconnect_called = False - def mock_disconnect(): - nonlocal disconnect_called - disconnect_called = True + result = db_instance.execute(query, *args, **kwargs) + + mock_pyodbc_connect.execute.assert_called_once_with(query, *args, **kwargs) + mock_pyodbc_connect.fetchall.assert_called_once() + assert result == expected_result + + def test_execute_with_fetch_error(self, db_instance, mock_pyodbc_connect, caplog): + mock_pyodbc_connect.fetchall.side_effect = Exception("Fetch error") - monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) - db_instance.connection = True + result = db_instance.execute("SELECT * FROM test_table") - db_instance.__exit__(None, None, None) - assert disconnect_called + assert result == [] + assert "Failed to execute query: Fetch error" in caplog.text + + def test_execute_commits_changes(self, db_instance, monkeypatch): + mock_connection = Mock() + mock_cursor = Mock() + mock_connection.cursor.return_value = mock_cursor + db_instance.connection = mock_connection + db_instance.cursor = mock_cursor + + db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") + + mock_cursor.execute.assert_called_once_with("INSERT INTO test_table VALUES (1, 'test')") + mock_connection.commit.assert_called_once() + + def test_set_driver_no_drivers(self, db_instance): + with patch.object(db_instance, '_Db__get_list_of_available_and_supported_pyodbc_drivers', return_value=[]): + with pytest.raises(ValueError, match="No supported ODBC drivers found."): + db_instance._Db__set_driver(0) + + def test_set_driver_invalid_index(self, db_instance): + with patch.object(db_instance, '_Db__get_list_of_available_and_supported_pyodbc_drivers', return_value=['Driver1']): + with pytest.raises(ValueError, match="Driver index 1 is out of range."): + db_instance._Db__set_driver(1) def test_set_driver_with_valid_index(self, monkeypatch, db_instance): available_drivers = ['DRIVER1', 'DRIVER2'] @@ -203,9 +419,27 @@ def test_set_driver_with_valid_index(self, monkeypatch, db_instance): db_instance._Db__set_driver(1) assert db_instance.driver == 'DRIVER2' + def test_get_number_of_available_pyodbc_drivers(self, db_instance): + with patch.object(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', return_value=['Driver1', 'Driver2']): + assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 2 + def test_get_number_of_available_pyodbc_drivers(self, db_instance, monkeypatch): - monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: ['DRIVER1', 'DRIVER2']) - assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 2 + monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) + assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 3 + + def test_get_list_of_supported_pyodbc_drivers(self, db_instance, monkeypatch): + mock_drivers = ['Driver1', 'Driver2', 'Driver3'] + monkeypatch.setattr(pyodbc, 'drivers', lambda: mock_drivers) + + result = db_instance._Db__get_list_of_supported_pyodbc_drivers() + assert result == mock_drivers + + def test_get_list_of_supported_pyodbc_drivers_error(self, db_instance, monkeypatch, caplog): + monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=pyodbc.Error("Test error"))) + + result = db_instance._Db__get_list_of_supported_pyodbc_drivers() + assert result == [] + assert "Error retrieving drivers: Test error" in caplog.text @patch('pyodbc.connect') def test_get_available_and_supported_drivers(self, mock_connect, db_instance): @@ -217,6 +451,28 @@ def test_get_available_and_supported_drivers(self, mock_connect, db_instance): assert result == ['Driver1', 'Driver3'] assert mock_connect.call_count == 3 + @patch('pyodbc.connect') + def test_get_list_of_available_and_supported_pyodbc_drivers(self, mock_connect, db_instance, monkeypatch): + mock_drivers = ['Driver1', 'Driver2', 'Driver3'] + monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: mock_drivers) + mock_connect.side_effect = [None, pyodbc.Error("Test error"), None] + + result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() + assert result == ['Driver1', 'Driver3'] + assert mock_connect.call_count == 3 + + def test_get_list_of_available_and_supported_pyodbc_drivers_logs_unavailable(self, db_instance, monkeypatch, caplog): + mock_drivers = ['Driver1', 'Driver2'] + monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: mock_drivers) + monkeypatch.setattr(pyodbc, 'connect', Mock(side_effect=pyodbc.Error("Test error"))) + + with caplog.at_level(logging.INFO): + result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() + + assert result == [] + assert any("Driver Driver1 could not connect: Test error" in record.message for record in caplog.records) + assert any("Driver Driver2 could not connect: Test error" in record.message for record in caplog.records) + def test_get_list_of_available_and_supported_pyodbc_drivers_silently_passes_on_error(self, db_instance, monkeypatch): mock_error = pyodbc.Error("Mock Error") monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=mock_error)) @@ -232,24 +488,12 @@ def mock_connect(*args, **kwargs): connect_called = True return Mock(cursor=Mock()) + monkeypatch.setattr('pyodbc.connect', mock_connect) db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect) as mock: - db_instance._Db__connect() - assert mock.called - assert connect_called - - def test_exit_disconnects_when_connection_exists(self, db_instance, monkeypatch): - disconnect_called = False - - def mock_disconnect(): - nonlocal disconnect_called - disconnect_called = True - - monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) - db_instance.connection = True - - db_instance.__exit__(None, None, None) - assert disconnect_called, "__disconnect should be called when __exit__ is invoked with an active connection" + db_instance._Db__connect() + + assert db_instance.connection is not None + assert connect_called def test_connect_raises_programming_error_with_logging(self, db_instance, monkeypatch, caplog): def mock_connect(*args, **kwargs): @@ -335,19 +579,29 @@ def mock_connect(*args, **kwargs): with patch('pyodbc.connect', side_effect=mock_connect): db_instance._Db__connect() - assert "DatabaseError ('DatabaseError code', 'Test database error'): No message" in caplog.text - assert attempt_count == 3 + assert attempt_count == 3, "Should retry exactly three times before success" + assert db_instance.connection is not None, "Connection should be established after retries" + + assert "DatabaseError ('DatabaseError code', 'Test database error'): No message" in caplog.text, "Should log the database error message on retry attempts" + assert "Retrying connection..." in caplog.text, "Retry message should be logged" def test_connect_breaks_after_max_attempts_on_database_error(self, db_instance, monkeypatch): + attempt_count = 0 + def mock_connect(*args, **kwargs): + nonlocal attempt_count + attempt_count += 1 raise pyodbc.DatabaseError(("DatabaseError code", "Test database error")) db_instance.connection = None - db_instance.connection_attempts = 3 + db_instance.connection_attempts = 3 + with patch('pyodbc.connect', side_effect=mock_connect): with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): db_instance._Db__connect() + assert attempt_count == 3, "Should attempt exactly three connections before raising an error" + def test_connect_retry_on_generic_error(self, db_instance, monkeypatch): attempt_count = 0 @@ -364,27 +618,39 @@ def mock_connect(*args, **kwargs): assert mock.call_count == 3 def test_connect_raises_error_after_max_attempts(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.Error(("Error code", "Test error")) + raise pyodbc.Error("Connection error") db_instance.connection = None db_instance.connection_attempts = 3 with patch('pyodbc.connect', side_effect=mock_connect): with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): db_instance._Db__connect() + + def test_are_connection_attempts_reached(self, db_instance, caplog): + assert not db_instance._Db__are_connection_attempts_reached(1) + assert "Retrying connection..." in caplog.text - def test_connect_exits_early_if_connection_exists(self, db_instance, monkeypatch): - connect_called = False + assert db_instance._Db__are_connection_attempts_reached(3) + assert "Failed to connect to the DataWarehouse after 3 attempts." in caplog.text + + def test_connect_success_after_retries(self, db_instance, monkeypatch): + attempt_count = 0 def mock_connect(*args, **kwargs): - nonlocal connect_called - connect_called = True - return Mock() + nonlocal attempt_count + attempt_count += 1 + if attempt_count < 3: + raise pyodbc.OperationalError("Operational error") + return Mock(cursor=Mock()) + + db_instance.connection_attempts = 3 + db_instance.connection = None - db_instance.connection = Mock() with patch('pyodbc.connect', side_effect=mock_connect): - db_instance._Db__connect() + db_instance._Db__connect() - assert not connect_called, "pyodbc.connect should not be called if connection already exists" + assert attempt_count == 3, "Should attempt three connections before succeeding" + assert db_instance.connection is not None, "Connection should be established after retries" def test_disconnect(self, db_instance): mock_connection = Mock() @@ -401,4 +667,10 @@ def test_disconnect_without_connection(self, db_instance): db_instance.connection = None db_instance.cursor = None - db_instance._Db__disconnect() \ No newline at end of file + db_instance._Db__disconnect() + + def test_commit(self, db_instance): + mock_connection = Mock() + db_instance.connection = mock_connection + db_instance._Db__commit() + mock_connection.commit.assert_called_once() \ No newline at end of file diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index 583da8b..f5b5361 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -1,13 +1,22 @@ import pytest import unittest +import random +import inspect +import string import pyodbc import logging import datetime +from typing import Dict, List from unittest.mock import Mock, patch, MagicMock, call from pyprediktormapclient.dwh.dwh import DWH +from pyprediktormapclient.dwh.idwh import IDWH class TestCaseDWH: + @staticmethod + def grs(): + """Generate a random string.""" + return ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) @pytest.fixture def mock_pyodbc_connect(self): @@ -19,9 +28,18 @@ def mock_pyodbc_connect(self): yield mock_connect @pytest.fixture - def dwh_instance(self, mock_pyodbc_connect): + def mock_pyodbc_drivers(self, monkeypatch): + monkeypatch.setattr('pyodbc.drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) + + @pytest.fixture + def mock_get_drivers(self, monkeypatch): + monkeypatch.setattr('pyprediktorutilities.dwh.dwh.Db._Db__get_list_of_available_and_supported_pyodbc_drivers', + lambda self: ['Driver1']) + + @pytest.fixture + def dwh_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): with patch.object(DWH, '_DWH__initialize_context_services'): - return DWH("test_url", "test_db", "test_user", "test_pass", -1) + return DWH(self.grs(), self.grs(), self.grs(), self.grs()) @pytest.fixture def mock_iter_modules(self): @@ -61,6 +79,23 @@ def test_init_default_driver(self, mock_drivers, mock_connect): dwh = DWH("test_url", "test_db", "test_user", "test_pass") assert dwh.driver == "Driver1" + def test_dwh_implements_idwh(self, dwh_instance): + def compare_signatures(impl_method, abstract_method): + impl_sig = inspect.signature(impl_method) + abstract_sig = inspect.signature(abstract_method) + + assert impl_sig.return_annotation == abstract_sig.return_annotation + + impl_params = list(impl_sig.parameters.values())[1:] + abstract_params = list(abstract_sig.parameters.values())[1:] + assert impl_params == abstract_params + + assert hasattr(dwh_instance, 'version') + assert callable(dwh_instance.version) + compare_signatures(dwh_instance.version, IDWH.version) + + assert dwh_instance.version.__annotations__['return'] == Dict + @patch.object(DWH, 'fetch') def test_version_with_results(self, mock_fetch, dwh_instance): expected_result = { From 5de56e0f79ce1c12874cd1ffb5d9ced2d407136e Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 13 Sep 2024 15:58:21 +0200 Subject: [PATCH 20/35] Refactored Db class implementation and tests for IDWH interface. --- src/pyprediktormapclient/dwh/db.py | 18 +- src/pyprediktormapclient/dwh/idwh.py | 6 +- tests/dwh/db_test.py | 496 ++++++--------------------- tests/dwh/dwh_test.py | 10 +- 4 files changed, 122 insertions(+), 408 deletions(-) diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index 027e596..c125ef0 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -175,16 +175,12 @@ def __set_driver(self, driver_index: int) -> None: elif driver_index >= len(available_drivers): raise ValueError( f"Driver index {driver_index} is out of range. Please use " - f"the __get_list_of_available_pyodbc_drivers() method " + f"the __get_list_of_available_and_supported_pyodbc_drivers() method " f"to list all available drivers." ) else: self.driver = available_drivers[driver_index] - @validate_call - def __get_number_of_available_pyodbc_drivers(self) -> int: - return len(self.__get_list_of_supported_pyodbc_drivers()) - @validate_call def __get_list_of_supported_pyodbc_drivers(self) -> List[Any]: try: @@ -233,9 +229,13 @@ def __connect(self) -> None: while attempt < self.connection_attempts: try: self.connection = pyodbc.connect(self.connection_string) - self.cursor = self.connection.cursor() - logging.info("Connection successfull!") - return + if self.connection: # Ensure connection is valid before accessing cursor + self.cursor = self.connection.cursor() + logging.info(f"Connected to the database on attempt {attempt + 1}") + return + else: + logging.info(f"Connection is None on attempt {attempt + 1}") + raise pyodbc.Error("Failed to connect to the database") # Exceptions once thrown there is no point attempting except pyodbc.ProgrammingError as err: @@ -262,7 +262,7 @@ def __connect(self) -> None: ) attempt += 1 if self.__are_connection_attempts_reached(attempt): - raise + break except (pyodbc.DatabaseError, pyodbc.Error) as err: logger.error(f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") diff --git a/src/pyprediktormapclient/dwh/idwh.py b/src/pyprediktormapclient/dwh/idwh.py index 3129be3..2a65b1d 100644 --- a/src/pyprediktormapclient/dwh/idwh.py +++ b/src/pyprediktormapclient/dwh/idwh.py @@ -5,12 +5,12 @@ class IDWH(ABC): @abstractmethod def version(self) -> Dict: - pass + raise NotImplementedError("version method is not implemented") @abstractmethod def fetch(self, query: str, to_dataframe: bool = False) -> List: - pass + raise NotImplementedError("fetch method is not implemented") @abstractmethod def execute(self, query: str, *args, **kwargs) -> List: - pass + raise NotImplementedError("execute method is not implemented") diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 5395c80..5406fd7 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -37,38 +37,24 @@ def mock_get_drivers(self, monkeypatch): def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): return Db(self.grs(), self.grs(), self.grs(), self.grs()) - def test_init_successful(self, db_instance): - assert db_instance is not None - assert isinstance(db_instance.url, str) - assert isinstance(db_instance.database, str) - assert isinstance(db_instance.username, str) - assert isinstance(db_instance.driver, str) - - def test_init_no_drivers(self, monkeypatch): - monkeypatch.setattr('pyodbc.drivers', lambda: []) - monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: []) - - with pytest.raises(ValueError, match="No supported ODBC drivers found."): - Db(self.grs(), self.grs(), self.grs(), self.grs()) - - @pytest.mark.parametrize('error, expected_error, expected_log_message', [ - (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'DataError Error code: Error message'), - (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.Error, 'DatabaseError Error code: Error message'), - ]) - def test_init_connection_error(self, monkeypatch, error, expected_error, expected_log_message, caplog, mock_get_drivers): - monkeypatch.setattr('pyodbc.connect', Mock(side_effect=error)) - with pytest.raises(expected_error) as exc_info: - with caplog.at_level(logging.ERROR): - Db(self.grs(), self.grs(), self.grs(), self.grs()) - - if expected_error == pyodbc.Error: - assert str(exc_info.value) == "Failed to connect to the database" - else: - assert str(exc_info.value) == str(error) - - assert expected_log_message in caplog.text - if expected_error == pyodbc.Error: - assert "Failed to connect to the DataWarehouse after 3 attempts" in caplog.text + # @pytest.mark.parametrize('error, expected_error, expected_log_message', [ + # (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'DataError Error code: Error message'), + # (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.Error, 'DatabaseError Error code: Error message'), + # ]) + # def test_init_connection_error(self, monkeypatch, error, expected_error, expected_log_message, caplog, mock_get_drivers): + # monkeypatch.setattr('pyodbc.connect', Mock(side_effect=error)) + # with pytest.raises(expected_error) as exc_info: + # with caplog.at_level(logging.ERROR): + # Db(self.grs(), self.grs(), self.grs(), self.grs()) + + # if expected_error == pyodbc.Error: + # assert str(exc_info.value) == "Failed to connect to the database" + # else: + # assert str(exc_info.value) == str(error) + + # assert expected_log_message in caplog.text + # if expected_error == pyodbc.Error: + # assert "Failed to connect to the DataWarehouse after 3 attempts" in caplog.text def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( self, monkeypatch @@ -88,65 +74,13 @@ def test_init_with_out_of_range_driver_index(self, monkeypatch): with pytest.raises(ValueError, match="Driver index 1 is out of range."): Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) - def test_context_manager(self, db_instance): - with patch.object(db_instance, '_Db__disconnect') as mock_disconnect: - with db_instance: - pass - mock_disconnect.assert_called_once() - def test_context_manager_enter(self, db_instance): assert db_instance.__enter__() == db_instance - def test_context_manager_exit(self, db_instance, monkeypatch): - disconnect_called = False - def mock_disconnect(): - nonlocal disconnect_called - disconnect_called = True - - monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) - db_instance.connection = True - - db_instance.__exit__(None, None, None) - assert disconnect_called - - def test_exit_with_connection(self, db_instance): - mock_connection = Mock() - db_instance.connection = mock_connection - db_instance.__exit__(None, None, None) - mock_connection.close.assert_called_once() - assert db_instance.connection is None - def test_exit_without_connection(self, db_instance): db_instance.connection = None db_instance.__exit__(None, None, None) - def test_exit_disconnects_when_connection_exists(self, db_instance, monkeypatch): - disconnect_called = False - - def mock_disconnect(): - nonlocal disconnect_called - disconnect_called = True - - monkeypatch.setattr(db_instance, '_Db__disconnect', mock_disconnect) - db_instance.connection = True - - db_instance.__exit__(None, None, None) - assert disconnect_called, "__disconnect should be called when __exit__ is invoked with an active connection" - - def test_connect_exits_early_if_connection_exists(self, db_instance, monkeypatch): - connect_called = False - - def mock_connect(*args, **kwargs): - nonlocal connect_called - connect_called = True - return Mock() - - db_instance.connection = Mock() - with patch('pyodbc.connect', side_effect=mock_connect): - db_instance._Db__connect() - - assert not connect_called, "pyodbc.connect should not be called if connection already exists" - def test_exit_with_open_connection_and_cleanup(self, db_instance): mock_connection = Mock() db_instance.connection = mock_connection @@ -159,7 +93,7 @@ def test_idwh_abstract_methods(self): assert inspect.isabstract(IDWH) assert set(IDWH.__abstractmethods__) == {'version', 'fetch', 'execute'} - def test_db_implements_idwh(self, db_instance, mock_pyodbc_connect): + def test_db_implements_abstract_idwh(self, db_instance, mock_pyodbc_connect): def compare_signatures(impl_method, abstract_method): if hasattr(impl_method, '__wrapped__'): impl_method = impl_method.__wrapped__ @@ -210,90 +144,9 @@ def compare_signatures(impl_method, abstract_method): execute_result = db_instance.execute("INSERT INTO dummy_table VALUES (1, 'test')") assert isinstance(execute_result, list), "execute method should return a list" - @pytest.mark.parametrize('to_dataframe', [True, False]) - def test_fetch(self, db_instance, to_dataframe): - mock_cursor = Mock() - mock_cursor.description = [('col1',), ('col2',)] - mock_cursor.fetchall.return_value = [(1, 'a'), (2, 'b')] - mock_cursor.nextset.return_value = False - db_instance.cursor = mock_cursor - - result = db_instance.fetch('SELECT * FROM test', to_dataframe) - - if to_dataframe: - assert isinstance(result, pd.DataFrame) - assert result.to_dict('records') == [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}] - else: - assert result == [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}] - - def test_fetch_connection_error(self, db_instance, monkeypatch): - monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.DataError('Error', 'Connection Error'))) - with pytest.raises(pyodbc.DataError): - db_instance.fetch("SELECT * FROM test_table") - - @pytest.mark.parametrize('to_dataframe', [False, True]) - def test_fetch_multiple_result_sets(self, db_instance, mock_pyodbc_connect, to_dataframe): - data1 = [(1, 'a'), (2, 'b')] - data2 = [(3, 'c'), (4, 'd')] - mock_pyodbc_connect.fetchall.side_effect = [data1, data2] - mock_pyodbc_connect.nextset.side_effect = [True, False] - mock_pyodbc_connect.description = [('col1',), ('col2',)] - - result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) - - if to_dataframe: - expected1 = pd.DataFrame(data1, columns=["col1", "col2"]) - expected2 = pd.DataFrame(data2, columns=["col1", "col2"]) - assert len(result) == 2 - assert_frame_equal(result[0], expected1) - assert_frame_equal(result[1], expected2) - else: - expected = [ - [{'col1': 1, 'col2': 'a'}, {'col1': 2, 'col2': 'b'}], - [{'col1': 3, 'col2': 'c'}, {'col1': 4, 'col2': 'd'}] - ] - assert result == expected - - def test_fetch_no_columns(self, db_instance, mock_pyodbc_connect): - mock_pyodbc_connect.description = [] - mock_pyodbc_connect.fetchall.return_value = [] - mock_pyodbc_connect.nextset.return_value = False - - result = db_instance.fetch("SELECT * FROM empty_table") - - assert result == [] - - @pytest.mark.parametrize('to_dataframe, expected_result', [ - (False, []), - (True, pd.DataFrame()), - ]) - def test_fetch_no_data(self, db_instance, mock_pyodbc_connect, to_dataframe, expected_result): - mock_pyodbc_connect.fetchall.return_value = [] - mock_pyodbc_connect.nextset.return_value = False - mock_pyodbc_connect.description = [("column1", None), ("column2", None)] - - result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) - - if to_dataframe: - assert result.empty - else: - assert result == expected_result - - @pytest.mark.parametrize('to_dataframe', [False, True]) - def test_fetch_single_dataset(self, db_instance, mock_pyodbc_connect, to_dataframe): - data = [("value1", 1), ("value2", 2)] - mock_pyodbc_connect.fetchall.return_value = data - mock_pyodbc_connect.nextset.return_value = False - mock_pyodbc_connect.description = [("column1", None), ("column2", None)] - - result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) - - if to_dataframe: - expected = pd.DataFrame(data, columns=["column1", "column2"]) - assert_frame_equal(result, expected) - else: - expected = [{"column1": "value1", "column2": 1}, {"column1": "value2", "column2": 2}] - assert result == expected + def test_idwh_instantiation_raises_error(self): + with pytest.raises(TypeError, match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'"): + IDWH() @pytest.mark.parametrize('to_dataframe', [False, True]) def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_dataframe): @@ -318,70 +171,6 @@ def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_data ] assert result == expected - def test_execute(self, db_instance): - mock_cursor = Mock() - mock_cursor.fetchall.return_value = [(1,)] - db_instance.cursor = mock_cursor - db_instance.connection = Mock() - - result = db_instance.execute('INSERT INTO test VALUES (?)', 'value') - - mock_cursor.execute.assert_called_with('INSERT INTO test VALUES (?)', 'value') - db_instance.connection.commit.assert_called_once() - assert result == [(1,)] - - def test_execute_sql_syntax_error(self, db_instance, mock_pyodbc_connect): - mock_pyodbc_connect.execute.side_effect = pyodbc.ProgrammingError("Syntax error") - - result = db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") - - assert result == [] - mock_pyodbc_connect.execute.assert_called_once_with("INSERT INTO test_table VALUES (1, 'test')") - - def test_execute_connection_error(self, db_instance, monkeypatch): - monkeypatch.setattr(db_instance, '_Db__connect', Mock(side_effect=pyodbc.Error('Error', 'Connection Error'))) - with pytest.raises(pyodbc.Error): - db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") - - def test_execute_with_parameters(self, db_instance, mock_pyodbc_connect): - query = "INSERT INTO test_table VALUES (?, ?)" - params = ("John", "Smith") - expected_result = [{"id": 13}] - mock_pyodbc_connect.fetchall.return_value = expected_result - - result = db_instance.execute(query, *params) - - mock_pyodbc_connect.execute.assert_called_once_with(query, *params) - mock_pyodbc_connect.fetchall.assert_called_once() - assert result == expected_result - - def test_execute_fetchall_error(self, db_instance, mock_pyodbc_connect): - query = "INSERT INTO test_table VALUES (?, ?)" - params = ("John", "Smith") - mock_pyodbc_connect.fetchall.side_effect = Exception("Error occurred") - - result = db_instance.execute(query, *params) - - mock_pyodbc_connect.execute.assert_called_once_with(query, *params) - mock_pyodbc_connect.fetchall.assert_called_once() - assert result == [] - - @pytest.mark.parametrize('query, args, kwargs', [ - ("INSERT INTO test_table VALUES (?, ?)", ('value1', 'value2'), {}), - ("UPDATE test_table SET column1 = ?", ('new_value',), {}), - ("DELETE FROM test_table WHERE id = ?", (1,), {}), - ("EXEC stored_procedure @param1=?, @param2=?", (), {'param1': 'value1', 'param2': 'value2'}), - ]) - def test_execute_with_various_queries(self, db_instance, mock_pyodbc_connect, query, args, kwargs): - expected_result = [{'id': 1}] - mock_pyodbc_connect.fetchall.return_value = expected_result - - result = db_instance.execute(query, *args, **kwargs) - - mock_pyodbc_connect.execute.assert_called_once_with(query, *args, **kwargs) - mock_pyodbc_connect.fetchall.assert_called_once() - assert result == expected_result - def test_execute_with_fetch_error(self, db_instance, mock_pyodbc_connect, caplog): mock_pyodbc_connect.fetchall.side_effect = Exception("Fetch error") @@ -390,28 +179,6 @@ def test_execute_with_fetch_error(self, db_instance, mock_pyodbc_connect, caplog assert result == [] assert "Failed to execute query: Fetch error" in caplog.text - def test_execute_commits_changes(self, db_instance, monkeypatch): - mock_connection = Mock() - mock_cursor = Mock() - mock_connection.cursor.return_value = mock_cursor - db_instance.connection = mock_connection - db_instance.cursor = mock_cursor - - db_instance.execute("INSERT INTO test_table VALUES (1, 'test')") - - mock_cursor.execute.assert_called_once_with("INSERT INTO test_table VALUES (1, 'test')") - mock_connection.commit.assert_called_once() - - def test_set_driver_no_drivers(self, db_instance): - with patch.object(db_instance, '_Db__get_list_of_available_and_supported_pyodbc_drivers', return_value=[]): - with pytest.raises(ValueError, match="No supported ODBC drivers found."): - db_instance._Db__set_driver(0) - - def test_set_driver_invalid_index(self, db_instance): - with patch.object(db_instance, '_Db__get_list_of_available_and_supported_pyodbc_drivers', return_value=['Driver1']): - with pytest.raises(ValueError, match="Driver index 1 is out of range."): - db_instance._Db__set_driver(1) - def test_set_driver_with_valid_index(self, monkeypatch, db_instance): available_drivers = ['DRIVER1', 'DRIVER2'] monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: available_drivers) @@ -419,21 +186,6 @@ def test_set_driver_with_valid_index(self, monkeypatch, db_instance): db_instance._Db__set_driver(1) assert db_instance.driver == 'DRIVER2' - def test_get_number_of_available_pyodbc_drivers(self, db_instance): - with patch.object(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', return_value=['Driver1', 'Driver2']): - assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 2 - - def test_get_number_of_available_pyodbc_drivers(self, db_instance, monkeypatch): - monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) - assert db_instance._Db__get_number_of_available_pyodbc_drivers() == 3 - - def test_get_list_of_supported_pyodbc_drivers(self, db_instance, monkeypatch): - mock_drivers = ['Driver1', 'Driver2', 'Driver3'] - monkeypatch.setattr(pyodbc, 'drivers', lambda: mock_drivers) - - result = db_instance._Db__get_list_of_supported_pyodbc_drivers() - assert result == mock_drivers - def test_get_list_of_supported_pyodbc_drivers_error(self, db_instance, monkeypatch, caplog): monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=pyodbc.Error("Test error"))) @@ -451,181 +203,154 @@ def test_get_available_and_supported_drivers(self, mock_connect, db_instance): assert result == ['Driver1', 'Driver3'] assert mock_connect.call_count == 3 - @patch('pyodbc.connect') - def test_get_list_of_available_and_supported_pyodbc_drivers(self, mock_connect, db_instance, monkeypatch): - mock_drivers = ['Driver1', 'Driver2', 'Driver3'] - monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: mock_drivers) - mock_connect.side_effect = [None, pyodbc.Error("Test error"), None] - - result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() - assert result == ['Driver1', 'Driver3'] - assert mock_connect.call_count == 3 - - def test_get_list_of_available_and_supported_pyodbc_drivers_logs_unavailable(self, db_instance, monkeypatch, caplog): - mock_drivers = ['Driver1', 'Driver2'] - monkeypatch.setattr(db_instance, '_Db__get_list_of_supported_pyodbc_drivers', lambda: mock_drivers) - monkeypatch.setattr(pyodbc, 'connect', Mock(side_effect=pyodbc.Error("Test error"))) - - with caplog.at_level(logging.INFO): - result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() - - assert result == [] - assert any("Driver Driver1 could not connect: Test error" in record.message for record in caplog.records) - assert any("Driver Driver2 could not connect: Test error" in record.message for record in caplog.records) - - def test_get_list_of_available_and_supported_pyodbc_drivers_silently_passes_on_error(self, db_instance, monkeypatch): - mock_error = pyodbc.Error("Mock Error") - monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=mock_error)) - - result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() - assert result == [], "Should return an empty list when pyodbc.Error occurs" - def test_connect_success(self, db_instance, monkeypatch): - connect_called = False - - def mock_connect(*args, **kwargs): - nonlocal connect_called - connect_called = True - return Mock(cursor=Mock()) - - monkeypatch.setattr('pyodbc.connect', mock_connect) + mock_connection = Mock() + monkeypatch.setattr('pyodbc.connect', lambda *args, **kwargs: mock_connection) + db_instance.connection = None db_instance._Db__connect() - assert db_instance.connection is not None - assert connect_called + assert db_instance.connection is mock_connection + assert db_instance.cursor is not None - def test_connect_raises_programming_error_with_logging(self, db_instance, monkeypatch, caplog): + def test_connect_raises_data_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.ProgrammingError("some_code", "some_message") - - monkeypatch.setattr(pyodbc, 'connect', mock_connect) + raise pyodbc.DataError("Data error") + monkeypatch.setattr('pyodbc.connect', mock_connect) + db_instance.connection = None - with pytest.raises(pyodbc.ProgrammingError): - db_instance._Db__connect() - - assert "Programming Error some_code: some_message" in caplog.text, "Programming error should be logged" - assert "There seems to be a problem with your code" in caplog.text, "Warning for ProgrammingError should be logged" + with pytest.raises(pyodbc.DataError): + db_instance._Db__connect() - def test_connect_raise_on_data_error(self, db_instance, monkeypatch): + def test_connect_raises_integrity_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.DataError(("DataError code", "Test data error")) + raise pyodbc.IntegrityError("Integrity error") + monkeypatch.setattr('pyodbc.connect', mock_connect) + db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.DataError): - db_instance._Db__connect() + with pytest.raises(pyodbc.IntegrityError): + db_instance._Db__connect() - def test_connect_raise_on_integrity_error(self, db_instance, monkeypatch): + def test_connect_raises_programming_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.IntegrityError(("IntegrityError code", "Test integrity error")) + raise pyodbc.ProgrammingError("Programming error") + monkeypatch.setattr('pyodbc.connect', mock_connect) + db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.IntegrityError): - db_instance._Db__connect() + with pytest.raises(pyodbc.ProgrammingError): + db_instance._Db__connect() - def test_connect_raise_on_not_supported_error(self, db_instance, monkeypatch): + def test_connect_raises_not_supported_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.NotSupportedError(("NotSupportedError code", "Test not supported error")) + raise pyodbc.NotSupportedError("Not supported error") + monkeypatch.setattr('pyodbc.connect', mock_connect) + db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.NotSupportedError): - db_instance._Db__connect() + with pytest.raises(pyodbc.NotSupportedError): + db_instance._Db__connect() - def test_connect_attempts_three_times_on_operational_error(self, db_instance, monkeypatch, caplog): + def test_connect_retries_on_operational_error(self, db_instance, monkeypatch): attempt_count = 0 def mock_connect(*args, **kwargs): nonlocal attempt_count attempt_count += 1 if attempt_count < 3: - exc = pyodbc.OperationalError() - exc.args = ("OperationalError code", "Mock Operational Error") - raise exc - else: - return Mock() + raise pyodbc.OperationalError("Operational error") + return Mock() + db_instance.connection_attempts = 3 db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect) as mock: + + with patch('pyodbc.connect', side_effect=mock_connect): db_instance._Db__connect() - assert "Operational Error: OperationalError code: Mock Operational Error" in caplog.text - assert "Pyodbc is having issues with the connection" in caplog.text - assert mock.call_count == 3 + + assert attempt_count == 3, "Should attempt three connections before succeeding" + assert db_instance.connection is not None, "Connection should be established after retries" def test_connect_raises_after_max_attempts_on_operational_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.OperationalError(("OperationalError code", "Test operational error")) + raise pyodbc.OperationalError("Operational error") db_instance.connection = None db_instance.connection_attempts = 3 with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.OperationalError): + with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): db_instance._Db__connect() - def test_connect_logs_database_error_and_retries(self, db_instance, monkeypatch, caplog): + def test_connect_retries_on_database_error(self, db_instance, monkeypatch): attempt_count = 0 def mock_connect(*args, **kwargs): nonlocal attempt_count attempt_count += 1 if attempt_count < 3: - raise pyodbc.DatabaseError(("DatabaseError code", "Test database error")) - return Mock(cursor=Mock()) + raise pyodbc.DatabaseError("Database error") + return Mock() + db_instance.connection_attempts = 3 db_instance.connection = None + with patch('pyodbc.connect', side_effect=mock_connect): db_instance._Db__connect() - assert attempt_count == 3, "Should retry exactly three times before success" - assert db_instance.connection is not None, "Connection should be established after retries" - - assert "DatabaseError ('DatabaseError code', 'Test database error'): No message" in caplog.text, "Should log the database error message on retry attempts" - assert "Retrying connection..." in caplog.text, "Retry message should be logged" - - def test_connect_breaks_after_max_attempts_on_database_error(self, db_instance, monkeypatch): - attempt_count = 0 - - def mock_connect(*args, **kwargs): - nonlocal attempt_count - attempt_count += 1 - raise pyodbc.DatabaseError(("DatabaseError code", "Test database error")) + assert attempt_count == 3, "Should attempt three connections before succeeding" - db_instance.connection = None - db_instance.connection_attempts = 3 - - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): - db_instance._Db__connect() + # def test_connect_raises_after_max_attempts_on_database_error(self, db_instance, monkeypatch): + # def mock_connect(*args, **kwargs): + # raise pyodbc.DatabaseError("Database error") - assert attempt_count == 3, "Should attempt exactly three connections before raising an error" + # db_instance.connection = None + # db_instance.connection_attempts = 3 + # with patch('pyodbc.connect', side_effect=mock_connect): + # with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + # db_instance._Db__connect() - def test_connect_retry_on_generic_error(self, db_instance, monkeypatch): + def test_connect_retries_on_generic_error(self, db_instance, monkeypatch): attempt_count = 0 def mock_connect(*args, **kwargs): nonlocal attempt_count attempt_count += 1 if attempt_count < 3: - raise pyodbc.Error(("Error code", "Test generic error")) - return Mock(cursor=Mock()) + raise pyodbc.Error("Generic error") + return Mock() + db_instance.connection_attempts = 3 db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect) as mock: + + with patch('pyodbc.connect', side_effect=mock_connect): db_instance._Db__connect() - assert mock.call_count == 3 - def test_connect_raises_error_after_max_attempts(self, db_instance, monkeypatch): + + assert attempt_count == 3, "Should attempt three connections before succeeding" + + def test_connect_raises_after_max_attempts_on_generic_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): - raise pyodbc.Error("Connection error") + raise pyodbc.Error("Generic error") db_instance.connection = None db_instance.connection_attempts = 3 with patch('pyodbc.connect', side_effect=mock_connect): with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): db_instance._Db__connect() - + + def test_connect_raises_error_when_connection_is_none(self, db_instance, monkeypatch): + + def mock_connect(*args, **kwargs): + return None + + monkeypatch.setattr('pyodbc.connect', mock_connect) + + db_instance.connection_attempts = 3 + db_instance.connection = None + + with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + db_instance._Db__connect() + def test_are_connection_attempts_reached(self, db_instance, caplog): assert not db_instance._Db__are_connection_attempts_reached(1) assert "Retrying connection..." in caplog.text @@ -633,25 +358,6 @@ def test_are_connection_attempts_reached(self, db_instance, caplog): assert db_instance._Db__are_connection_attempts_reached(3) assert "Failed to connect to the DataWarehouse after 3 attempts." in caplog.text - def test_connect_success_after_retries(self, db_instance, monkeypatch): - attempt_count = 0 - - def mock_connect(*args, **kwargs): - nonlocal attempt_count - attempt_count += 1 - if attempt_count < 3: - raise pyodbc.OperationalError("Operational error") - return Mock(cursor=Mock()) - - db_instance.connection_attempts = 3 - db_instance.connection = None - - with patch('pyodbc.connect', side_effect=mock_connect): - db_instance._Db__connect() - - assert attempt_count == 3, "Should attempt three connections before succeeding" - assert db_instance.connection is not None, "Connection should be established after retries" - def test_disconnect(self, db_instance): mock_connection = Mock() db_instance.connection = mock_connection diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index f5b5361..2405a75 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -79,7 +79,11 @@ def test_init_default_driver(self, mock_drivers, mock_connect): dwh = DWH("test_url", "test_db", "test_user", "test_pass") assert dwh.driver == "Driver1" - def test_dwh_implements_idwh(self, dwh_instance): + def test_idwh_abstract_methods(self): + assert inspect.isabstract(IDWH) + assert set(IDWH.__abstractmethods__) == {'version', 'fetch', 'execute'} + + def test_dwh_implements_abstract_idwh(self, dwh_instance): def compare_signatures(impl_method, abstract_method): impl_sig = inspect.signature(impl_method) abstract_sig = inspect.signature(abstract_method) @@ -96,6 +100,10 @@ def compare_signatures(impl_method, abstract_method): assert dwh_instance.version.__annotations__['return'] == Dict + def test_idwh_instantiation_raises_error(self): + with pytest.raises(TypeError, match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'"): + IDWH() + @patch.object(DWH, 'fetch') def test_version_with_results(self, mock_fetch, dwh_instance): expected_result = { From d63ddde6e31cd6d0aa3c730fde2d4640a634859b Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 16 Sep 2024 15:59:03 +0200 Subject: [PATCH 21/35] Refactored tests and fixtures. --- .coveragerc | 2 +- pyproject.toml | 2 +- src/pyprediktormapclient/dwh/db.py | 49 ++- src/pyprediktormapclient/opc_ua.py | 28 +- tests/conftest.py | 9 +- tests/dwh/db_test.py | 405 ++++++++++++++-------- tests/dwh/dwh_test.py | 213 +++++++----- tests/opc_ua_test.py | 522 +++++++++++++++++++---------- 8 files changed, 803 insertions(+), 427 deletions(-) diff --git a/.coveragerc b/.coveragerc index 593f1fb..ea9f002 100644 --- a/.coveragerc +++ b/.coveragerc @@ -27,4 +27,4 @@ exclude_lines = if 0: if __name__ == .__main__.: -show_missing = True \ No newline at end of file +show_missing = True diff --git a/pyproject.toml b/pyproject.toml index d0f8165..0868715 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,4 +12,4 @@ version_scheme = "no-guess-dev" pythonpath = [ "src" ] -asyncio_default_fixture_loop_scope = "function" \ No newline at end of file +asyncio_default_fixture_loop_scope = "function" diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index c125ef0..0bb124f 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -102,7 +102,9 @@ def fetch(self, query: str, to_dataframe: bool = False) -> List[Any]: data_sets = [] while True: columns = [col[0] for col in self.cursor.description] - data_set = [dict(zip(columns, row)) for row in self.cursor.fetchall()] + data_set = [ + dict(zip(columns, row)) for row in self.cursor.fetchall() + ] if to_dataframe: data_sets.append(pd.DataFrame(data_set, columns=columns)) @@ -161,15 +163,17 @@ def __set_driver(self, driver_index: int) -> None: driver (int): Index of the driver in the list of available drivers. If the index is -1 or in general below 0, pyPrediktorMapClient is going to choose the driver for you. - + Raises: ValueError: If no valid driver is found. """ - available_drivers = self.__get_list_of_available_and_supported_pyodbc_drivers() - + available_drivers = ( + self.__get_list_of_available_and_supported_pyodbc_drivers() + ) + if not available_drivers: raise ValueError("No supported ODBC drivers found.") - + if driver_index < 0: self.driver = available_drivers[0] elif driver_index >= len(available_drivers): @@ -191,7 +195,7 @@ def __get_list_of_supported_pyodbc_drivers(self) -> List[Any]: @validate_call def __get_list_of_available_and_supported_pyodbc_drivers( - self + self, ) -> List[Any]: available_drivers = [] supported_drivers = self.__get_list_of_supported_pyodbc_drivers() @@ -209,7 +213,6 @@ def __get_list_of_available_and_supported_pyodbc_drivers( available_drivers.append(driver) except pyodbc.Error as err: logger.info(f"Driver {driver} could not connect: {err}") - pass return available_drivers @@ -229,30 +232,44 @@ def __connect(self) -> None: while attempt < self.connection_attempts: try: self.connection = pyodbc.connect(self.connection_string) - if self.connection: # Ensure connection is valid before accessing cursor + if self.connection: self.cursor = self.connection.cursor() - logging.info(f"Connected to the database on attempt {attempt + 1}") + logging.info( + f"Connected to the database on attempt {attempt + 1}" + ) return else: - logging.info(f"Connection is None on attempt {attempt + 1}") + logging.info( + f"Connection is None on attempt {attempt + 1}" + ) raise pyodbc.Error("Failed to connect to the database") # Exceptions once thrown there is no point attempting except pyodbc.ProgrammingError as err: - logger.error(f"Programming Error {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") + logger.error( + f"Programming Error {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}" + ) logger.warning( "There seems to be a problem with your code. Please " "check your code and try again." ) raise - except (pyodbc.DataError, pyodbc.IntegrityError, pyodbc.NotSupportedError) as err: - logger.error(f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") + except ( + pyodbc.DataError, + pyodbc.IntegrityError, + pyodbc.NotSupportedError, + ) as err: + logger.error( + f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}" + ) raise # Exceptions when thrown we can continue attempting except pyodbc.OperationalError as err: - logger.error(f"Operational Error: {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") + logger.error( + f"Operational Error: {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}" + ) logger.warning( "Pyodbc is having issues with the connection. This " "could be due to the wrong driver being used. Please " @@ -265,7 +282,9 @@ def __connect(self) -> None: break except (pyodbc.DatabaseError, pyodbc.Error) as err: - logger.error(f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}") + logger.error( + f"{type(err).__name__} {err.args[0] if err.args else 'No code'}: {err.args[1] if len(err.args) > 1 else 'No message'}" + ) attempt += 1 if self.__are_connection_attempts_reached(attempt): break diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index 6231994..fb8b76b 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -205,7 +205,7 @@ def __init__( if not str(self.opcua_url).startswith("opc.tcp://"): raise ValueError("Invalid OPC UA URL") - + if self.auth_client is not None: if self.auth_client.token is not None: self.headers["Authorization"] = ( @@ -364,7 +364,9 @@ def _check_content(self, content: Dict[str, Any]) -> None: if not content.get("Success"): raise RuntimeError(content.get("ErrorMessage")) if "HistoryReadResults" not in content: - raise RuntimeError("No history read results returned from the server") + raise RuntimeError( + "No history read results returned from the server" + ) def _process_df( self, df_result: pd.DataFrame, columns: Dict[str, str] @@ -635,7 +637,7 @@ def write_values(self, variable_list: List[WriteVariables]) -> List: raise RuntimeError(f"Error in write_values: {str(e)}") except Exception as e: raise RuntimeError(f"Error in write_values: {str(e)}") - + # Return if no content from server if not isinstance(content, dict): return None @@ -666,13 +668,17 @@ def write_historical_values( """ # Check if data is in correct order, if wrong fail. for variable in variable_list: - if len(variable.get('UpdateValues', [])) > 1: - for num_variable in range(len(variable['UpdateValues']) - 1): + if len(variable.get("UpdateValues", [])) > 1: + for num_variable in range(len(variable["UpdateValues"]) - 1): if not ( - (variable['UpdateValues'][num_variable]['SourceTimestamp']) - < variable['UpdateValues'][ - num_variable + 1 - ]['SourceTimestamp'] + ( + variable["UpdateValues"][num_variable][ + "SourceTimestamp" + ] + ) + < variable["UpdateValues"][num_variable + 1][ + "SourceTimestamp" + ] ): raise ValueError( "Time for variables not in correct order." @@ -703,7 +709,9 @@ def write_historical_values( extended_timeout=True, ) else: - raise RuntimeError(f"Error in write_historical_values: {str(e)}") + raise RuntimeError( + f"Error in write_historical_values: {str(e)}" + ) except Exception as e: raise RuntimeError(f"Error in write_historical_values: {str(e)}") # Return if no content from server diff --git a/tests/conftest.py b/tests/conftest.py index 039d77f..c0c30b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,19 +6,18 @@ URL = "http://someserver.somedomain.com/v1/" OPC_URL = "opc.tcp://nosuchserver.nosuchdomain.com" + @pytest.fixture def opc(): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) return opc + @pytest.fixture def mock_error_response(): response = AsyncMock() response.text = AsyncMock(return_value="Error Message") response.raise_for_status.side_effect = ClientResponseError( - request_info=AsyncMock(), - history=(), - status=0, - message="Error Message" + request_info=AsyncMock(), history=(), status=0, message="Error Message" ) - return response \ No newline at end of file + return response diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 5406fd7..2a950ec 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -5,73 +5,119 @@ import inspect import logging import pandas as pd -from unittest.mock import Mock, patch, MagicMock -from typing import Dict, List, Any, get_origin, get_args +from unittest.mock import Mock, patch +from typing import List, Any, get_origin, get_args from pyprediktormapclient.dwh.db import Db from pyprediktormapclient.dwh.idwh import IDWH from pandas.testing import assert_frame_equal + class TestCaseDB: @staticmethod def grs(): """Generate a random string.""" - return ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) + return "".join( + random.choices(string.ascii_uppercase + string.digits, k=10) + ) @pytest.fixture def mock_pyodbc_connect(self, monkeypatch): mock_connection = Mock() mock_cursor = Mock() mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr('pyodbc.connect', lambda *args, **kwargs: mock_connection) + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) return mock_cursor @pytest.fixture def mock_pyodbc_drivers(self, monkeypatch): - monkeypatch.setattr('pyodbc.drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) @pytest.fixture def mock_get_drivers(self, monkeypatch): - monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: ['Driver1']) + monkeypatch.setattr( + Db, + "_Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: ["Driver1"], + ) @pytest.fixture def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): return Db(self.grs(), self.grs(), self.grs(), self.grs()) - # @pytest.mark.parametrize('error, expected_error, expected_log_message', [ - # (pyodbc.DataError('Error code', 'Error message'), pyodbc.DataError, 'DataError Error code: Error message'), - # (pyodbc.DatabaseError('Error code', 'Error message'), pyodbc.Error, 'DatabaseError Error code: Error message'), - # ]) - # def test_init_connection_error(self, monkeypatch, error, expected_error, expected_log_message, caplog, mock_get_drivers): - # monkeypatch.setattr('pyodbc.connect', Mock(side_effect=error)) - # with pytest.raises(expected_error) as exc_info: - # with caplog.at_level(logging.ERROR): - # Db(self.grs(), self.grs(), self.grs(), self.grs()) - - # if expected_error == pyodbc.Error: - # assert str(exc_info.value) == "Failed to connect to the database" - # else: - # assert str(exc_info.value) == str(error) - - # assert expected_log_message in caplog.text - # if expected_error == pyodbc.Error: - # assert "Failed to connect to the DataWarehouse after 3 attempts" in caplog.text + @pytest.mark.parametrize( + "error, expected_error, expected_log_message", + [ + ( + pyodbc.DataError("Error code", "Error message"), + pyodbc.DataError, + "DataError Error code: Error message", + ), + ( + pyodbc.DatabaseError("Error code", "Error message"), + pyodbc.Error, + "DatabaseError Error code: Error message", + ), + ], + ) + + def test_init_connection_error( + self, + monkeypatch, + error, + expected_error, + expected_log_message, + caplog, + mock_get_drivers, + ): + monkeypatch.setattr("pyodbc.connect", Mock(side_effect=error)) + with pytest.raises(expected_error) as exc_info: + with caplog.at_level(logging.ERROR): + Db(self.grs(), self.grs(), self.grs(), self.grs()) + + if expected_error == pyodbc.Error: + assert str(exc_info.value) == "Failed to connect to the database" + else: + assert str(exc_info.value) == str(error) + + assert expected_log_message in caplog.text + if expected_error == pyodbc.Error: + assert ( + "Failed to connect to the DataWarehouse after 3 attempts" + in caplog.text + ) def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( self, monkeypatch ): driver_index = 0 - monkeypatch.setattr('pyodbc.drivers', lambda: ['DRIVER1']) - monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: []) - - with pytest.raises(ValueError, match="No supported ODBC drivers found."): + monkeypatch.setattr("pyodbc.drivers", lambda: ["DRIVER1"]) + monkeypatch.setattr( + Db, + "_Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: [], + ) + + with pytest.raises( + ValueError, match="No supported ODBC drivers found." + ): Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) def test_init_with_out_of_range_driver_index(self, monkeypatch): driver_index = 1 - monkeypatch.setattr('pyodbc.drivers', lambda: ['DRIVER1']) - monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: ['DRIVER1']) - - with pytest.raises(ValueError, match="Driver index 1 is out of range."): + monkeypatch.setattr("pyodbc.drivers", lambda: ["DRIVER1"]) + monkeypatch.setattr( + Db, + "_Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: ["DRIVER1"], + ) + + with pytest.raises( + ValueError, match="Driver index 1 is out of range." + ): Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) def test_context_manager_enter(self, db_instance): @@ -87,74 +133,114 @@ def test_exit_with_open_connection_and_cleanup(self, db_instance): db_instance.__exit__(None, None, None) mock_connection.close.assert_called_once() - assert db_instance.connection is None, "Connection should be set to None after exit" + assert ( + db_instance.connection is None + ), "Connection should be set to None after exit" def test_idwh_abstract_methods(self): assert inspect.isabstract(IDWH) - assert set(IDWH.__abstractmethods__) == {'version', 'fetch', 'execute'} + assert set(IDWH.__abstractmethods__) == {"version", "fetch", "execute"} - def test_db_implements_abstract_idwh(self, db_instance, mock_pyodbc_connect): + def test_db_implements_abstract_idwh( + self, db_instance, mock_pyodbc_connect + ): def compare_signatures(impl_method, abstract_method): - if hasattr(impl_method, '__wrapped__'): + if hasattr(impl_method, "__wrapped__"): impl_method = impl_method.__wrapped__ impl_sig = inspect.signature(impl_method) abstract_sig = inspect.signature(abstract_method) - + impl_return = impl_sig.return_annotation abstract_return = abstract_sig.return_annotation - + impl_origin = get_origin(impl_return) or impl_return abstract_origin = get_origin(abstract_return) or abstract_origin - - assert impl_origin == abstract_origin, f"Return type mismatch: {impl_return} is not compatible with {abstract_return}" - + + assert ( + impl_origin == abstract_origin + ), f"Return type mismatch: {impl_return} is not compatible with {abstract_return}" + if impl_origin is List: impl_args = get_args(impl_return) abstract_args = get_args(abstract_return) - + if not abstract_args: abstract_args = (Any,) - - assert len(impl_args) == len(abstract_args), f"Generic argument count mismatch: {impl_args} vs {abstract_args}" + + assert len(impl_args) == len( + abstract_args + ), f"Generic argument count mismatch: {impl_args} vs {abstract_args}" for impl_arg, abstract_arg in zip(impl_args, abstract_args): - assert impl_arg == abstract_arg or abstract_arg == Any, f"Generic argument mismatch: {impl_arg} is not compatible with {abstract_arg}" - - impl_params = list(impl_sig.parameters.values())[1:] - abstract_params = list(abstract_sig.parameters.values())[1:] - - assert len(impl_params) == len(abstract_params), f"Parameter count mismatch: implementation has {len(impl_params)}, abstract has {len(abstract_params)}" - - for impl_param, abstract_param in zip(impl_params, abstract_params): - assert impl_param.name == abstract_param.name, f"Parameter name mismatch: {impl_param.name} != {abstract_param.name}" - assert impl_param.annotation == abstract_param.annotation, f"Parameter type mismatch for {impl_param.name}: {impl_param.annotation} != {abstract_param.annotation}" - assert impl_param.default == abstract_param.default, f"Parameter default value mismatch for {impl_param.name}: {impl_param.default} != {abstract_param.default}" - - for method_name in ['fetch', 'execute']: - assert hasattr(db_instance, method_name), f"Db class is missing method: {method_name}" - assert callable(getattr(db_instance, method_name)), f"Db.{method_name} is not callable" - compare_signatures(getattr(db_instance, method_name), getattr(IDWH, method_name)) - - mock_pyodbc_connect.description = [('column1',), ('column2',)] - mock_pyodbc_connect.fetchall.return_value = [(1, 'a'), (2, 'b')] + assert ( + impl_arg == abstract_arg or abstract_arg == Any + ), f"Generic argument mismatch: {impl_arg} is not compatible with {abstract_arg}" + + impl_params = list(impl_sig.parameters.values())[1:] + abstract_params = list(abstract_sig.parameters.values())[1:] + + assert len(impl_params) == len( + abstract_params + ), f"Parameter count mismatch: implementation has {len(impl_params)}, abstract has {len(abstract_params)}" + + for impl_param, abstract_param in zip( + impl_params, abstract_params + ): + assert ( + impl_param.name == abstract_param.name + ), f"Parameter name mismatch: {impl_param.name} != {abstract_param.name}" + assert ( + impl_param.annotation == abstract_param.annotation + ), f"Parameter type mismatch for {impl_param.name}: {impl_param.annotation} != {abstract_param.annotation}" + assert ( + impl_param.default == abstract_param.default + ), f"Parameter default value mismatch for {impl_param.name}: {impl_param.default} != {abstract_param.default}" + + for method_name in ["fetch", "execute"]: + assert hasattr( + db_instance, method_name + ), f"Db class is missing method: {method_name}" + assert callable( + getattr(db_instance, method_name) + ), f"Db.{method_name} is not callable" + compare_signatures( + getattr(db_instance, method_name), getattr(IDWH, method_name) + ) + + mock_pyodbc_connect.description = [("column1",), ("column2",)] + mock_pyodbc_connect.fetchall.return_value = [(1, "a"), (2, "b")] mock_pyodbc_connect.nextset.return_value = False fetch_result = db_instance.fetch("SELECT * FROM dummy_table") - assert isinstance(fetch_result, list), "fetch method should return a list" - execute_result = db_instance.execute("INSERT INTO dummy_table VALUES (1, 'test')") - assert isinstance(execute_result, list), "execute method should return a list" + assert isinstance( + fetch_result, list + ), "fetch method should return a list" + execute_result = db_instance.execute( + "INSERT INTO dummy_table VALUES (1, 'test')" + ) + assert isinstance( + execute_result, list + ), "execute method should return a list" def test_idwh_instantiation_raises_error(self): - with pytest.raises(TypeError, match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'"): + with pytest.raises( + TypeError, + match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'", + ): IDWH() - @pytest.mark.parametrize('to_dataframe', [False, True]) - def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_dataframe): + @pytest.mark.parametrize("to_dataframe", [False, True]) + def test_fetch_multiple_datasets( + self, db_instance, mock_pyodbc_connect, to_dataframe + ): data1 = [("value1", 1), ("value2", 2)] data2 = [("value3", 3), ("value4", 4)] mock_pyodbc_connect.fetchall.side_effect = [data1, data2] mock_pyodbc_connect.nextset.side_effect = [True, False] - mock_pyodbc_connect.description = [("column1", None), ("column2", None)] + mock_pyodbc_connect.description = [ + ("column1", None), + ("column2", None), + ] result = db_instance.fetch("SELECT * FROM test_table", to_dataframe) @@ -166,50 +252,74 @@ def test_fetch_multiple_datasets(self, db_instance, mock_pyodbc_connect, to_data assert_frame_equal(result[1], expected2) else: expected = [ - [{"column1": "value1", "column2": 1}, {"column1": "value2", "column2": 2}], - [{"column1": "value3", "column2": 3}, {"column1": "value4", "column2": 4}] + [ + {"column1": "value1", "column2": 1}, + {"column1": "value2", "column2": 2}, + ], + [ + {"column1": "value3", "column2": 3}, + {"column1": "value4", "column2": 4}, + ], ] assert result == expected - def test_execute_with_fetch_error(self, db_instance, mock_pyodbc_connect, caplog): + def test_execute_with_fetch_error( + self, db_instance, mock_pyodbc_connect, caplog + ): mock_pyodbc_connect.fetchall.side_effect = Exception("Fetch error") - + result = db_instance.execute("SELECT * FROM test_table") - + assert result == [] assert "Failed to execute query: Fetch error" in caplog.text def test_set_driver_with_valid_index(self, monkeypatch, db_instance): - available_drivers = ['DRIVER1', 'DRIVER2'] - monkeypatch.setattr(Db, '_Db__get_list_of_available_and_supported_pyodbc_drivers', lambda self: available_drivers) - + available_drivers = ["DRIVER1", "DRIVER2"] + monkeypatch.setattr( + Db, + "_Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: available_drivers, + ) + db_instance._Db__set_driver(1) - assert db_instance.driver == 'DRIVER2' + assert db_instance.driver == "DRIVER2" + + def test_get_list_of_supported_pyodbc_drivers_error( + self, db_instance, monkeypatch, caplog + ): + monkeypatch.setattr( + pyodbc, "drivers", Mock(side_effect=pyodbc.Error("Test error")) + ) - def test_get_list_of_supported_pyodbc_drivers_error(self, db_instance, monkeypatch, caplog): - monkeypatch.setattr(pyodbc, 'drivers', Mock(side_effect=pyodbc.Error("Test error"))) - result = db_instance._Db__get_list_of_supported_pyodbc_drivers() assert result == [] assert "Error retrieving drivers: Test error" in caplog.text - @patch('pyodbc.connect') - def test_get_available_and_supported_drivers(self, mock_connect, db_instance): - db_instance.__get_list_of_supported_pyodbc_drivers = Mock(return_value=['Driver1', 'Driver2', 'Driver3']) + @patch("pyodbc.connect") + def test_get_available_and_supported_drivers( + self, mock_connect, db_instance + ): + db_instance.__get_list_of_supported_pyodbc_drivers = Mock( + return_value=["Driver1", "Driver2", "Driver3"] + ) mock_connect.side_effect = [None, pyodbc.Error, None] - - result = db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() - - assert result == ['Driver1', 'Driver3'] + + result = ( + db_instance._Db__get_list_of_available_and_supported_pyodbc_drivers() + ) + + assert result == ["Driver1", "Driver3"] assert mock_connect.call_count == 3 def test_connect_success(self, db_instance, monkeypatch): mock_connection = Mock() - monkeypatch.setattr('pyodbc.connect', lambda *args, **kwargs: mock_connection) - + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + db_instance.connection = None db_instance._Db__connect() - + assert db_instance.connection is mock_connection assert db_instance.cursor is not None @@ -217,8 +327,8 @@ def test_connect_raises_data_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): raise pyodbc.DataError("Data error") - monkeypatch.setattr('pyodbc.connect', mock_connect) - + monkeypatch.setattr("pyodbc.connect", mock_connect) + db_instance.connection = None with pytest.raises(pyodbc.DataError): db_instance._Db__connect() @@ -227,8 +337,8 @@ def test_connect_raises_integrity_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): raise pyodbc.IntegrityError("Integrity error") - monkeypatch.setattr('pyodbc.connect', mock_connect) - + monkeypatch.setattr("pyodbc.connect", mock_connect) + db_instance.connection = None with pytest.raises(pyodbc.IntegrityError): db_instance._Db__connect() @@ -237,23 +347,27 @@ def test_connect_raises_programming_error(self, db_instance, monkeypatch): def mock_connect(*args, **kwargs): raise pyodbc.ProgrammingError("Programming error") - monkeypatch.setattr('pyodbc.connect', mock_connect) - + monkeypatch.setattr("pyodbc.connect", mock_connect) + db_instance.connection = None with pytest.raises(pyodbc.ProgrammingError): db_instance._Db__connect() - def test_connect_raises_not_supported_error(self, db_instance, monkeypatch): + def test_connect_raises_not_supported_error( + self, db_instance, monkeypatch + ): def mock_connect(*args, **kwargs): raise pyodbc.NotSupportedError("Not supported error") - monkeypatch.setattr('pyodbc.connect', mock_connect) - + monkeypatch.setattr("pyodbc.connect", mock_connect) + db_instance.connection = None with pytest.raises(pyodbc.NotSupportedError): db_instance._Db__connect() - def test_connect_retries_on_operational_error(self, db_instance, monkeypatch): + def test_connect_retries_on_operational_error( + self, db_instance, monkeypatch + ): attempt_count = 0 def mock_connect(*args, **kwargs): @@ -266,20 +380,28 @@ def mock_connect(*args, **kwargs): db_instance.connection_attempts = 3 db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): + with patch("pyodbc.connect", side_effect=mock_connect): db_instance._Db__connect() - assert attempt_count == 3, "Should attempt three connections before succeeding" - assert db_instance.connection is not None, "Connection should be established after retries" + assert ( + attempt_count == 3 + ), "Should attempt three connections before succeeding" + assert ( + db_instance.connection is not None + ), "Connection should be established after retries" - def test_connect_raises_after_max_attempts_on_operational_error(self, db_instance, monkeypatch): + def test_connect_raises_after_max_attempts_on_operational_error( + self, db_instance, monkeypatch + ): def mock_connect(*args, **kwargs): raise pyodbc.OperationalError("Operational error") db_instance.connection = None db_instance.connection_attempts = 3 - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + with patch("pyodbc.connect", side_effect=mock_connect): + with pytest.raises( + pyodbc.Error, match="Failed to connect to the database" + ): db_instance._Db__connect() def test_connect_retries_on_database_error(self, db_instance, monkeypatch): @@ -295,20 +417,26 @@ def mock_connect(*args, **kwargs): db_instance.connection_attempts = 3 db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): + with patch("pyodbc.connect", side_effect=mock_connect): db_instance._Db__connect() - assert attempt_count == 3, "Should attempt three connections before succeeding" + assert ( + attempt_count == 3 + ), "Should attempt three connections before succeeding" - # def test_connect_raises_after_max_attempts_on_database_error(self, db_instance, monkeypatch): - # def mock_connect(*args, **kwargs): - # raise pyodbc.DatabaseError("Database error") + def test_connect_raises_after_max_attempts_on_database_error( + self, db_instance, monkeypatch + ): + def mock_connect(*args, **kwargs): + raise pyodbc.DatabaseError("Database error") - # db_instance.connection = None - # db_instance.connection_attempts = 3 - # with patch('pyodbc.connect', side_effect=mock_connect): - # with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): - # db_instance._Db__connect() + db_instance.connection = None + db_instance.connection_attempts = 3 + with patch("pyodbc.connect", side_effect=mock_connect): + with pytest.raises( + pyodbc.Error, match="Failed to connect to the database" + ): + db_instance._Db__connect() def test_connect_retries_on_generic_error(self, db_instance, monkeypatch): attempt_count = 0 @@ -323,32 +451,42 @@ def mock_connect(*args, **kwargs): db_instance.connection_attempts = 3 db_instance.connection = None - with patch('pyodbc.connect', side_effect=mock_connect): + with patch("pyodbc.connect", side_effect=mock_connect): db_instance._Db__connect() - assert attempt_count == 3, "Should attempt three connections before succeeding" + assert ( + attempt_count == 3 + ), "Should attempt three connections before succeeding" - def test_connect_raises_after_max_attempts_on_generic_error(self, db_instance, monkeypatch): + def test_connect_raises_after_max_attempts_on_generic_error( + self, db_instance, monkeypatch + ): def mock_connect(*args, **kwargs): raise pyodbc.Error("Generic error") db_instance.connection = None db_instance.connection_attempts = 3 - with patch('pyodbc.connect', side_effect=mock_connect): - with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + with patch("pyodbc.connect", side_effect=mock_connect): + with pytest.raises( + pyodbc.Error, match="Failed to connect to the database" + ): db_instance._Db__connect() - def test_connect_raises_error_when_connection_is_none(self, db_instance, monkeypatch): - + def test_connect_raises_error_when_connection_is_none( + self, db_instance, monkeypatch + ): + def mock_connect(*args, **kwargs): return None - - monkeypatch.setattr('pyodbc.connect', mock_connect) - + + monkeypatch.setattr("pyodbc.connect", mock_connect) + db_instance.connection_attempts = 3 db_instance.connection = None - - with pytest.raises(pyodbc.Error, match="Failed to connect to the database"): + + with pytest.raises( + pyodbc.Error, match="Failed to connect to the database" + ): db_instance._Db__connect() def test_are_connection_attempts_reached(self, db_instance, caplog): @@ -356,7 +494,10 @@ def test_are_connection_attempts_reached(self, db_instance, caplog): assert "Retrying connection..." in caplog.text assert db_instance._Db__are_connection_attempts_reached(3) - assert "Failed to connect to the DataWarehouse after 3 attempts." in caplog.text + assert ( + "Failed to connect to the DataWarehouse after 3 attempts." + in caplog.text + ) def test_disconnect(self, db_instance): mock_connection = Mock() @@ -379,4 +520,4 @@ def test_commit(self, db_instance): mock_connection = Mock() db_instance.connection = mock_connection db_instance._Db__commit() - mock_connection.commit.assert_called_once() \ No newline at end of file + mock_connection.commit.assert_called_once() diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index 2405a75..3ea12e4 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -6,7 +6,7 @@ import pyodbc import logging import datetime -from typing import Dict, List +from typing import Dict from unittest.mock import Mock, patch, MagicMock, call from pyprediktormapclient.dwh.dwh import DWH from pyprediktormapclient.dwh.idwh import IDWH @@ -16,11 +16,13 @@ class TestCaseDWH: @staticmethod def grs(): """Generate a random string.""" - return ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) + return "".join( + random.choices(string.ascii_uppercase + string.digits, k=10) + ) @pytest.fixture def mock_pyodbc_connect(self): - with patch('pyodbc.connect') as mock_connect: + with patch("pyodbc.connect") as mock_connect: mock_connection = Mock() mock_cursor = Mock() mock_connection.cursor.return_value = mock_cursor @@ -29,50 +31,66 @@ def mock_pyodbc_connect(self): @pytest.fixture def mock_pyodbc_drivers(self, monkeypatch): - monkeypatch.setattr('pyodbc.drivers', lambda: ['Driver1', 'Driver2', 'Driver3']) + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) @pytest.fixture def mock_get_drivers(self, monkeypatch): - monkeypatch.setattr('pyprediktorutilities.dwh.dwh.Db._Db__get_list_of_available_and_supported_pyodbc_drivers', - lambda self: ['Driver1']) + monkeypatch.setattr( + "pyprediktorutilities.dwh.dwh.Db._Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: ["Driver1"], + ) @pytest.fixture def dwh_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): - with patch.object(DWH, '_DWH__initialize_context_services'): + with patch.object(DWH, "_DWH__initialize_context_services"): return DWH(self.grs(), self.grs(), self.grs(), self.grs()) - + @pytest.fixture def mock_iter_modules(self): - with patch('pkgutil.iter_modules') as mock_iter_modules: + with patch("pkgutil.iter_modules") as mock_iter_modules: yield mock_iter_modules - @patch.object(DWH, '_DWH__initialize_context_services') - @patch('pyprediktormapclient.dwh.dwh.Db.__init__') - def test_init(self, mock_db_init, mock_initialize_context_services, mock_pyodbc_connect): + @patch.object(DWH, "_DWH__initialize_context_services") + @patch("pyprediktormapclient.dwh.dwh.Db.__init__") + def test_init( + self, + mock_db_init, + mock_initialize_context_services, + mock_pyodbc_connect, + ): DWH("test_url", "test_db", "test_user", "test_pass", -1) - mock_db_init.assert_called_once_with("test_url", "test_db", "test_user", "test_pass", -1) + mock_db_init.assert_called_once_with( + "test_url", "test_db", "test_user", "test_pass", -1 + ) mock_initialize_context_services.assert_called_once() - @patch('pyprediktorutilities.dwh.dwh.pyodbc.connect') + @patch("pyprediktorutilities.dwh.dwh.pyodbc.connect") def test_init_connection_error(self, mock_connect): - mock_connect.side_effect = pyodbc.DataError("Error code", "Error message") + mock_connect.side_effect = pyodbc.DataError( + "Error code", "Error message" + ) with pytest.raises(pyodbc.DataError): DWH("test_url", "test_db", "test_user", "test_pass", 0) - @patch('pyprediktorutilities.dwh.dwh.pyodbc.connect') + @patch("pyprediktorutilities.dwh.dwh.pyodbc.connect") def test_init_connection_retry(self, mock_connect, caplog): mock_connect.side_effect = [ pyodbc.DatabaseError("Error code", "Temporary error message"), pyodbc.DatabaseError("Error code", "Temporary error message"), - pyodbc.DatabaseError("Error code", "Permanent error message") + pyodbc.DatabaseError("Error code", "Permanent error message"), ] with caplog.at_level(logging.ERROR): with pytest.raises(pyodbc.DatabaseError): DWH("test_url", "test_db", "test_user", "test_pass", 0) - assert "Failed to connect to the DataWarehouse after 3 attempts." in caplog.text + assert ( + "Failed to connect to the DataWarehouse after 3 attempts." + in caplog.text + ) - @patch('pyodbc.connect') - @patch('pyodbc.drivers') + @patch("pyodbc.connect") + @patch("pyodbc.drivers") def test_init_default_driver(self, mock_drivers, mock_connect): mock_drivers.return_value = ["Driver1", "Driver2"] mock_connect.return_value = Mock() @@ -81,7 +99,7 @@ def test_init_default_driver(self, mock_drivers, mock_connect): def test_idwh_abstract_methods(self): assert inspect.isabstract(IDWH) - assert set(IDWH.__abstractmethods__) == {'version', 'fetch', 'execute'} + assert set(IDWH.__abstractmethods__) == {"version", "fetch", "execute"} def test_dwh_implements_abstract_idwh(self, dwh_instance): def compare_signatures(impl_method, abstract_method): @@ -94,17 +112,20 @@ def compare_signatures(impl_method, abstract_method): abstract_params = list(abstract_sig.parameters.values())[1:] assert impl_params == abstract_params - assert hasattr(dwh_instance, 'version') + assert hasattr(dwh_instance, "version") assert callable(dwh_instance.version) compare_signatures(dwh_instance.version, IDWH.version) - assert dwh_instance.version.__annotations__['return'] == Dict + assert dwh_instance.version.__annotations__["return"] == Dict def test_idwh_instantiation_raises_error(self): - with pytest.raises(TypeError, match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'"): + with pytest.raises( + TypeError, + match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'", + ): IDWH() - @patch.object(DWH, 'fetch') + @patch.object(DWH, "fetch") def test_version_with_results(self, mock_fetch, dwh_instance): expected_result = { "DWHVersion": "2.3.1", @@ -117,61 +138,71 @@ def test_version_with_results(self, mock_fetch, dwh_instance): mock_fetch.return_value = [expected_result] version = dwh_instance.version() assert version == expected_result - mock_fetch.assert_called_once_with("SET NOCOUNT ON; EXEC [dbo].[GetVersion]") + mock_fetch.assert_called_once_with( + "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" + ) - @patch.object(DWH, 'fetch') + @patch.object(DWH, "fetch") def test_version_without_results(self, mock_fetch, dwh_instance): mock_fetch.return_value = [] version = dwh_instance.version() assert version == {} - mock_fetch.assert_called_once_with("SET NOCOUNT ON; EXEC [dbo].[GetVersion]") - - - @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') - def test_initialize_context_services(self, mock_import_module, mock_iter_modules, dwh_instance): + mock_fetch.assert_called_once_with( + "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" + ) + + @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") + def test_initialize_context_services( + self, mock_import_module, mock_iter_modules, dwh_instance + ): mock_iter_modules.return_value = [ - (None, 'pyprediktormapclient.dwh.context.enercast', False), - (None, 'pyprediktormapclient.dwh.context.plant', False), - (None, 'pyprediktormapclient.dwh.context.solcast', False) + (None, "pyprediktormapclient.dwh.context.enercast", False), + (None, "pyprediktormapclient.dwh.context.plant", False), + (None, "pyprediktormapclient.dwh.context.solcast", False), ] - + class TestService: def __init__(self, dwh): self.dwh = dwh def mock_import(name): mock_module = MagicMock() - mock_module.__dir__ = lambda *args: ['TestService'] + mock_module.__dir__ = lambda *args: ["TestService"] mock_module.TestService = TestService return mock_module mock_import_module.side_effect = mock_import - with patch.object(dwh_instance, '_is_attr_valid_service_class', return_value=True): + with patch.object( + dwh_instance, "_is_attr_valid_service_class", return_value=True + ): dwh_instance._DWH__initialize_context_services() expected_calls = [ - call('pyprediktormapclient.dwh.context.enercast'), - call('pyprediktormapclient.dwh.context.plant'), - call('pyprediktormapclient.dwh.context.solcast') + call("pyprediktormapclient.dwh.context.enercast"), + call("pyprediktormapclient.dwh.context.plant"), + call("pyprediktormapclient.dwh.context.solcast"), ] - assert all(expected_call in mock_import_module.call_args_list for expected_call in expected_calls), \ - "Not all expected module imports were made" - assert hasattr(dwh_instance, 'enercast') - assert hasattr(dwh_instance, 'plant') - assert hasattr(dwh_instance, 'solcast') - - for attr in ['enercast', 'plant', 'solcast']: + assert all( + expected_call in mock_import_module.call_args_list + for expected_call in expected_calls + ), "Not all expected module imports were made" + assert hasattr(dwh_instance, "enercast") + assert hasattr(dwh_instance, "plant") + assert hasattr(dwh_instance, "solcast") + + for attr in ["enercast", "plant", "solcast"]: assert isinstance(getattr(dwh_instance, attr), TestService) assert getattr(dwh_instance, attr).dwh == dwh_instance - - @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') - def test_initialize_context_services_with_modules(self, mock_import_module, mock_iter_modules, dwh_instance): + @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") + def test_initialize_context_services_with_modules( + self, mock_import_module, mock_iter_modules, dwh_instance + ): mock_iter_modules.return_value = [ - (None, 'pyprediktormapclient.dwh.context.enercast', False), - (None, 'pyprediktormapclient.dwh.context.plant', False), - (None, 'pyprediktormapclient.dwh.context.solcast', False), + (None, "pyprediktormapclient.dwh.context.enercast", False), + (None, "pyprediktormapclient.dwh.context.plant", False), + (None, "pyprediktormapclient.dwh.context.solcast", False), ] class TestService: @@ -180,50 +211,72 @@ def __init__(self, dwh): def mock_import(name): mock_module = MagicMock() - mock_module.__dir__ = lambda *args: ['TestService'] + mock_module.__dir__ = lambda *args: ["TestService"] mock_module.TestService = TestService return mock_module mock_import_module.side_effect = mock_import dwh_instance._DWH__initialize_context_services() - expected_modules = ['pyprediktormapclient.dwh.context.enercast', - 'pyprediktormapclient.dwh.context.plant', - 'pyprediktormapclient.dwh.context.solcast'] - imported_modules = [call[0][0] for call in mock_import_module.call_args_list] - - assert all(module in imported_modules for module in expected_modules), "Not all expected modules were imported" - assert hasattr(dwh_instance, 'enercast'), "enercast attribute is missing" - assert hasattr(dwh_instance, 'plant'), "plant attribute is missing" - assert hasattr(dwh_instance, 'solcast'), "solcast attribute is missing" - - for attr in ['enercast', 'plant', 'solcast']: - assert isinstance(getattr(dwh_instance, attr), TestService), f"{attr} is not an instance of TestService" - assert getattr(dwh_instance, attr).dwh == dwh_instance, f"{attr}'s dwh is not the dwh_instance" - - - @patch('pyprediktormapclient.dwh.dwh.importlib.import_module') - def test_initialize_context_services_with_package(self, mock_import_module, mock_iter_modules, dwh_instance): + expected_modules = [ + "pyprediktormapclient.dwh.context.enercast", + "pyprediktormapclient.dwh.context.plant", + "pyprediktormapclient.dwh.context.solcast", + ] + imported_modules = [ + call[0][0] for call in mock_import_module.call_args_list + ] + + assert all( + module in imported_modules for module in expected_modules + ), "Not all expected modules were imported" + assert hasattr( + dwh_instance, "enercast" + ), "enercast attribute is missing" + assert hasattr(dwh_instance, "plant"), "plant attribute is missing" + assert hasattr(dwh_instance, "solcast"), "solcast attribute is missing" + + for attr in ["enercast", "plant", "solcast"]: + assert isinstance( + getattr(dwh_instance, attr), TestService + ), f"{attr} is not an instance of TestService" + assert ( + getattr(dwh_instance, attr).dwh == dwh_instance + ), f"{attr}'s dwh is not the dwh_instance" + + @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") + def test_initialize_context_services_with_package( + self, mock_import_module, mock_iter_modules, dwh_instance + ): mock_iter_modules.return_value = [ - (None, 'pyprediktormapclient.dwh.context.package', True), + (None, "pyprediktormapclient.dwh.context.package", True), ] dwh_instance._DWH__initialize_context_services() - imported_modules = [call[0][0] for call in mock_import_module.call_args_list] - assert 'pyprediktormapclient.dwh.context.package' not in imported_modules, "Package was unexpectedly imported" - assert not hasattr(dwh_instance, 'package'), "package attribute unexpectedly exists" + imported_modules = [ + call[0][0] for call in mock_import_module.call_args_list + ] + assert ( + "pyprediktormapclient.dwh.context.package" not in imported_modules + ), "Package was unexpectedly imported" + assert not hasattr( + dwh_instance, "package" + ), "package attribute unexpectedly exists" def test_is_attr_valid_service_class(self, dwh_instance): class TestClass: pass - + class IDWH: pass assert dwh_instance._is_attr_valid_service_class(TestClass) is True - assert dwh_instance._is_attr_valid_service_class(IDWH) is True - assert dwh_instance._is_attr_valid_service_class('not_a_class') is False + assert dwh_instance._is_attr_valid_service_class(IDWH) is True + assert ( + dwh_instance._is_attr_valid_service_class("not_a_class") is False + ) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 70c8c49..af964c9 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -13,13 +13,11 @@ import asyncio import requests import json -import logging import pandas as pd -from parameterized import parameterized -from aiohttp.client_exceptions import ClientResponseError, ClientError +from aiohttp.client_exceptions import ClientResponseError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA, WriteVariables, TYPE_LIST +from pyprediktormapclient.opc_ua import OPC_UA, TYPE_LIST from pyprediktormapclient.auth_client import AUTH_CLIENT, Token URL = "http://someserver.somedomain.com/v1/" @@ -493,7 +491,9 @@ class TestCaseOPCUA: def setup(self, opc): self.opc = opc self.auth_client_mock = Mock() - self.opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + self.opc_auth = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock + ) opc.TYPE_DICT = {t["id"]: t["type"] for t in TYPE_LIST} def test_malformed_rest_url(self): @@ -516,27 +516,33 @@ def test_invalid_opcua_url(self): def test_namespaces(self, opc): assert "ClientNamespaces" not in opc.body - opc_with_namespaces = OPC_UA(rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"]) + opc_with_namespaces = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, namespaces=["1", "2"] + ) assert "ClientNamespaces" in opc_with_namespaces.body assert opc_with_namespaces.body["ClientNamespaces"] == ["1", "2"] def test_init_with_auth_client(self): self.auth_client_mock.token = Mock() self.auth_client_mock.token.session_token = "test_token" - self.opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + self.opc_auth = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock + ) assert self.opc_auth.auth_client == self.auth_client_mock assert self.opc_auth.headers["Authorization"] == "Bearer test_token" def test_init_with_auth_client_no_token(self): - self.auth_client_mock.token = None - opc_auth = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock) + self.auth_client_mock.token = None + opc_auth = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=self.auth_client_mock + ) - assert "Authorization" not in opc_auth.headers + assert "Authorization" not in opc_auth.headers def test_init_with_minimal_args(self): - assert self.opc.auth_client is None - assert "Authorization" not in self.opc.headers + assert self.opc.auth_client is None + assert "Authorization" not in self.opc.headers def test_json_serial_with_url(self): url = Url("http://example.com") @@ -558,8 +564,10 @@ def test_json_serial_with_unsupported_type(self): unsupported_type = set() with pytest.raises(TypeError) as exc_info: self.opc.json_serial(unsupported_type) - - expected_error_message = f"Type {type(unsupported_type)} not serializable" + + expected_error_message = ( + f"Type {type(unsupported_type)} not serializable" + ) assert str(exc_info.value) == expected_error_message def test_check_auth_client_404_error(self): @@ -596,7 +604,9 @@ def test_check_if_ory_session_token_is_valid_refresh_when_expired(self): self.auth_client_mock.check_if_token_has_expired.assert_called_once() self.auth_client_mock.refresh_token.assert_called_once() - def test_check_if_ory_session_token_is_valid_refresh_when_not_expired(self): + def test_check_if_ory_session_token_is_valid_refresh_when_not_expired( + self, + ): self.auth_client_mock.check_if_token_has_expired.return_value = False self.opc_auth.check_if_ory_session_token_is_valid_refresh() self.auth_client_mock.check_if_token_has_expired.assert_called_once() @@ -634,13 +644,17 @@ def test_get_variable_list_as_list(self): assert isinstance(result[0], dict) assert result[0] == dict_var - with pytest.raises(TypeError, match="Unsupported type in variable_list"): + with pytest.raises( + TypeError, match="Unsupported type in variable_list" + ): self.opc._get_variable_list_as_list([123]) def test_get_values_variable_list_not_list(self): not_a_list = "not_a_list" - with pytest.raises(TypeError, match="Unsupported type in variable_list"): + with pytest.raises( + TypeError, match="Unsupported type in variable_list" + ): self.opc.get_values(not_a_list) def test_get_variable_list_as_list_invalid_type(self): @@ -649,13 +663,16 @@ def test_get_variable_list_as_list_invalid_type(self): @pytest.mark.parametrize("auth_client_class", [None, AUTH_CLIENT]) @patch("requests.post", side_effect=successful_mocked_requests) - def test_get_live_values_successful_response_processing(self, mock_get, auth_client_class): + def test_get_live_values_successful_response_processing( + self, mock_get, auth_client_class + ): if auth_client_class: auth_client = auth_client_class( rest_url=URL, username="test_user", password="test_pass" ) auth_client.token = Token( - session_token="test_session_id", expires_at="2099-01-01T00:00:00Z" + session_token="test_session_id", + expires_at="2099-01-01T00:00:00Z", ) tsdata = OPC_UA( rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client @@ -664,38 +681,54 @@ def test_get_live_values_successful_response_processing(self, mock_get, auth_cli tsdata = OPC_UA(rest_url=URL, opcua_url=OPC_URL) result = tsdata.get_values(list_of_ids) - - assert len(result) == len(list_of_ids), "Result length should match input length" - + + assert len(result) == len( + list_of_ids + ), "Result length should match input length" + for num, row in enumerate(list_of_ids): - assert result[num]["Id"] == list_of_ids[num]["Id"], "IDs should match" assert ( - result[num]["Timestamp"] == - successful_live_response[0]["Values"][num]["ServerTimestamp"] + result[num]["Id"] == list_of_ids[num]["Id"] + ), "IDs should match" + assert ( + result[num]["Timestamp"] + == successful_live_response[0]["Values"][num][ + "ServerTimestamp" + ] ), "Timestamps should match" assert ( - result[num]["Value"] == - successful_live_response[0]["Values"][num]["Value"]["Body"] + result[num]["Value"] + == successful_live_response[0]["Values"][num]["Value"]["Body"] ), "Values should match" - @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_get_live_values_with_auth_client_error_handling(self, mock_request_from_api): + @patch("pyprediktormapclient.opc_ua.request_from_api") + def test_get_live_values_with_auth_client_error_handling( + self, mock_request_from_api + ): self.auth_client_mock.token.session_token = "test_token" - + mock_response = Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() mock_request_from_api.side_effect = [ - requests.exceptions.HTTPError("404 Client Error", response=mock_response), - successful_live_response + requests.exceptions.HTTPError( + "404 Client Error", response=mock_response + ), + successful_live_response, ] result = self.opc_auth.get_values(list_of_ids) - + assert result is not None - assert mock_request_from_api.call_count == 2, "request_from_api should be called twice due to retry" + assert ( + mock_request_from_api.call_count == 2 + ), "request_from_api should be called twice due to retry" - assert "Authorization" in self.opc_auth.headers, "Authorization header is missing" - assert "test_token" in self.opc_auth.headers.get("Authorization", ""), "Session token not found in Authorization header" + assert ( + "Authorization" in self.opc_auth.headers + ), "Authorization header is missing" + assert "test_token" in self.opc_auth.headers.get( + "Authorization", "" + ), "Session token not found in Authorization header" @patch("requests.post", side_effect=empty_values_mocked_requests) def test_get_live_values_with_missing_values(self, mock_get): @@ -707,33 +740,60 @@ def test_get_live_values_with_missing_values(self, mock_get): result[num]["Timestamp"] == empty_live_response[0]["Values"][num]["ServerTimestamp"] ) - assert all(result[num][key] is None for key in ["Value", "ValueType"]) + assert all( + result[num][key] is None for key in ["Value", "ValueType"] + ) @patch("requests.post", side_effect=no_status_code_mocked_requests) def test_get_live_values_no_status_code(self, mock_get): result = self.opc.get_values(list_of_ids) assert result[0]["StatusCode"] is None assert result[0]["StatusSymbol"] is None - assert all(result[0][key] is not None for key in ["Id", "Timestamp", "Value", "ValueType"]) + assert all( + result[0][key] is not None + for key in ["Id", "Timestamp", "Value", "ValueType"] + ) @patch("requests.post", side_effect=no_mocked_requests) def test_get_live_values_no_response(self, mock_get): result = self.opc.get_values(list_of_ids) for item in result: - assert all(item[key] is None for key in ["Timestamp", "Value", "ValueType", "StatusCode", "StatusSymbol"]) + assert all( + item[key] is None + for key in [ + "Timestamp", + "Value", + "ValueType", + "StatusCode", + "StatusSymbol", + ] + ) assert "Success" not in result[0] @patch("requests.post", side_effect=empty_mocked_requests) def test_get_live_values_empty_response(self, mock_get): result = self.opc.get_values(list_of_ids) - - assert len(result) == len(list_of_ids), "Result should have same length as input" - + + assert len(result) == len( + list_of_ids + ), "Result should have same length as input" + for item in result: - assert all(item[key] is None for key in ["Timestamp", "Value", "ValueType", "StatusCode", "StatusSymbol"]) - assert all(item[key] is not None for key in ["Id", "Namespace", "IdType"]) + assert all( + item[key] is None + for key in [ + "Timestamp", + "Value", + "ValueType", + "StatusCode", + "StatusSymbol", + ] + ) + assert all( + item[key] is not None for key in ["Id", "Namespace", "IdType"] + ) - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_get_live_values_error_handling(self, mock_request): mock_request.side_effect = Exception("Test exception") with pytest.raises(RuntimeError) as exc_info: @@ -744,8 +804,10 @@ def test_get_live_values_error_handling(self, mock_request): def test_get_live_values_500_error(self, mock_post): error_response = Mock() error_response.status_code = 500 - error_response.raise_for_status.side_effect = HTTPError("500 Server Error: Internal Server Error for url") - + error_response.raise_for_status.side_effect = HTTPError( + "500 Server Error: Internal Server Error for url" + ) + mock_post.return_value = error_response with pytest.raises(RuntimeError): self.opc.get_values(list_of_ids) @@ -760,119 +822,150 @@ def test_check_content_valid(self): self.opc._check_content(valid_content) def test_check_content_not_dict(self): - with pytest.raises(RuntimeError, match="No content returned from the server"): + with pytest.raises( + RuntimeError, match="No content returned from the server" + ): self.opc._check_content("Not a dictionary") def test_check_content_not_successful(self): unsuccessful_content = successful_historical_result.copy() unsuccessful_content["Success"] = False unsuccessful_content["ErrorMessage"] = "Some error occurred" - + with pytest.raises(RuntimeError, match="Some error occurred"): self.opc._check_content(unsuccessful_content) def test_check_content_no_history_results(self): content_without_history = successful_historical_result.copy() del content_without_history["HistoryReadResults"] - - with pytest.raises(RuntimeError, match="No history read results returned from the server"): + + with pytest.raises( + RuntimeError, + match="No history read results returned from the server", + ): self.opc._check_content(content_without_history) def test_process_df_with_value_type(self): - df_input = pd.DataFrame({ - "Value.Type": [11, 12, 11], - "Value.Body": [1.23, "test", 4.56], - "OldColumn1": ["A", "B", "C"], - "OldColumn2": [1, 2, 3] - }) + df_input = pd.DataFrame( + { + "Value.Type": [11, 12, 11], + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + "OldColumn2": [1, 2, 3], + } + ) columns = {"OldColumn1": "NewColumn1", "OldColumn2": "NewColumn2"} - + result = self.opc._process_df(df_input, columns) - + assert list(result["Value.Type"]) == ["Double", "String", "Double"] - assert set(result.columns) == {"Value.Type", "Value.Body", "NewColumn1", "NewColumn2"} + assert set(result.columns) == { + "Value.Type", + "Value.Body", + "NewColumn1", + "NewColumn2", + } def test_process_df_without_value_type(self): - df_input = pd.DataFrame({ - "Value.Body": [1.23, "test", 4.56], - "OldColumn1": ["A", "B", "C"], - "OldColumn2": [1, 2, 3] - }) + df_input = pd.DataFrame( + { + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + "OldColumn2": [1, 2, 3], + } + ) columns = {"OldColumn1": "NewColumn1", "OldColumn2": "NewColumn2"} - + result = self.opc._process_df(df_input, columns) - - assert set(result.columns) == {"Value.Body", "NewColumn1", "NewColumn2"} + + assert set(result.columns) == { + "Value.Body", + "NewColumn1", + "NewColumn2", + } def test_process_df_rename_error(self): - df_input = pd.DataFrame({ - "Value.Body": [1.23, "test", 4.56], - "OldColumn1": ["A", "B", "C"], - }) + df_input = pd.DataFrame( + { + "Value.Body": [1.23, "test", 4.56], + "OldColumn1": ["A", "B", "C"], + } + ) columns = {"NonExistentColumn": "NewColumn"} - + with pytest.raises(KeyError): self.opc._process_df(df_input, columns) def test_process_df_empty_dataframe(self): df_input = pd.DataFrame() columns = {} - + result = self.opc._process_df(df_input, columns) - + assert result.empty assert isinstance(result, pd.DataFrame) def test_process_content(self): result = self.opc._process_content(successful_historical_result) - + assert isinstance(result, pd.DataFrame) assert len(result) == 4 - + assert "HistoryReadResults.NodeId.Id" in result.columns assert "HistoryReadResults.NodeId.Namespace" in result.columns assert "HistoryReadResults.NodeId.IdType" in result.columns - + assert result.iloc[0]["HistoryReadResults.NodeId.Id"] == "SOMEID" assert result.iloc[0]["Value.Body"] == 34.28500000000003 assert result.iloc[0]["SourceTimestamp"] == "2022-09-13T13:39:51Z" - + assert result.iloc[2]["HistoryReadResults.NodeId.Id"] == "SOMEID2" assert result.iloc[2]["Value.Body"] == 34.28500000000003 assert result.iloc[2]["SourceTimestamp"] == "2022-09-13T13:39:51Z" - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_write_live_values_http_error_handling(self, mock_request): auth_client_mock = Mock() - opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock) - + opc = OPC_UA( + rest_url=URL, opcua_url=OPC_URL, auth_client=auth_client_mock + ) + mock_response = Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() - http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) - mock_request.side_effect = [http_error, {"Success": True, "StatusCodes": [{"Code": 0}]}] - + http_error = requests.exceptions.HTTPError( + "404 Client Error", response=mock_response + ) + mock_request.side_effect = [ + http_error, + {"Success": True, "StatusCodes": [{"Code": 0}]}, + ] + opc.check_auth_client = Mock() - + result = opc.write_values(list_of_write_values) - + opc.check_auth_client.assert_called_once_with({"error": {"code": 404}}) assert result is not None assert len(result) == len(list_of_write_values) - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_write_live_values_error_handling(self, mock_request): mock_request.side_effect = Exception("Test exception") with pytest.raises(RuntimeError) as exc_info: self.opc.write_values(list_of_write_values) assert str(exc_info.value) == "Error in write_values: Test exception" - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_write_live_values_http_error_without_auth(self, mock_request): - mock_request.side_effect = requests.exceptions.HTTPError("404 Client Error") - + mock_request.side_effect = requests.exceptions.HTTPError( + "404 Client Error" + ) + with pytest.raises(RuntimeError) as exc_info: self.opc.write_values(list_of_write_values) - assert str(exc_info.value).startswith("Error in write_values: 404 Client Error") + assert str(exc_info.value).startswith( + "Error in write_values: 404 Client Error" + ) @patch("requests.post", side_effect=successful_write_mocked_requests) def test_write_live_values_successful(self, mock_get): @@ -888,9 +981,7 @@ def test_write_live_values_successful(self, mock_get): ) assert result[num]["WriteSuccess"] is True - @patch( - "requests.post", side_effect=empty_write_values_mocked_requests - ) + @patch("requests.post", side_effect=empty_write_values_mocked_requests) def test_write_live_values_with_missing_value_and_statuscode( self, mock_get ): @@ -903,9 +994,7 @@ def test_get_write_live_values_no_response(self, mock_get): result = self.opc.write_values(list_of_write_values) assert result is None - @patch( - "requests.post", side_effect=unsuccessful_write_mocked_requests - ) + @patch("requests.post", side_effect=unsuccessful_write_mocked_requests) def test_get_write_live_values_unsuccessful(self, mock_get): with pytest.raises(RuntimeError): self.opc.write_values(list_of_write_values) @@ -919,53 +1008,81 @@ def test_write_historical_values_succeeds_with_empty_update_values(self): empty_historical_variable = WriteHistoricalVariables( NodeId=Variables(**list_of_write_values[0]["NodeId"]), PerformInsertReplace=1, - UpdateValues=[] + UpdateValues=[], ) - - with patch('pyprediktormapclient.opc_ua.request_from_api') as mock_request: - mock_request.return_value = {"Success": True, "HistoryUpdateResults": [{}]} - result = self.opc.write_historical_values([empty_historical_variable.model_dump()]) - + + with patch( + "pyprediktormapclient.opc_ua.request_from_api" + ) as mock_request: + mock_request.return_value = { + "Success": True, + "HistoryUpdateResults": [{}], + } + result = self.opc.write_historical_values( + [empty_historical_variable.model_dump()] + ) + assert result is not None assert len(result) == 1 - assert result[0].get('WriteSuccess', False) + assert result[0].get("WriteSuccess", False) - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_write_historical_values_http_error_handling(self, mock_request): mock_response = Mock() mock_response.content = json.dumps({"error": {"code": 404}}).encode() - http_error = requests.exceptions.HTTPError("404 Client Error", response=mock_response) - mock_request.side_effect = [http_error, {"Success": True, "HistoryUpdateResults": [{}]}] - + http_error = requests.exceptions.HTTPError( + "404 Client Error", response=mock_response + ) + mock_request.side_effect = [ + http_error, + {"Success": True, "HistoryUpdateResults": [{}]}, + ] + self.opc_auth.check_auth_client = Mock() - - converted_data = [WriteHistoricalVariables(**item).model_dump() - for item in list_of_write_historical_values] + + converted_data = [ + WriteHistoricalVariables(**item).model_dump() + for item in list_of_write_historical_values + ] result = self.opc_auth.write_historical_values(converted_data) - - self.opc_auth.check_auth_client.assert_called_once_with({"error": {"code": 404}}) + + self.opc_auth.check_auth_client.assert_called_once_with( + {"error": {"code": 404}} + ) assert result is not None assert len(result) == len(converted_data) - @patch('pyprediktormapclient.opc_ua.request_from_api') + @patch("pyprediktormapclient.opc_ua.request_from_api") def test_error_handling_write_historical_values(self, mock_request): mock_request.side_effect = [ Exception("Test exception"), - Exception("Test exception") + Exception("Test exception"), + ] + input_data = [ + WriteHistoricalVariables(**item).model_dump() + for item in list_of_write_historical_values ] - input_data = [WriteHistoricalVariables(**item).model_dump() - for item in list_of_write_historical_values] with pytest.raises(Exception) as exc_info: self.opc.write_historical_values(input_data) - assert str(exc_info.value) == "Error in write_historical_values: Test exception" + assert ( + str(exc_info.value) + == "Error in write_historical_values: Test exception" + ) assert mock_request.call_count == 1 - @patch('pyprediktormapclient.opc_ua.request_from_api') - def test_write_historical_values_http_error_without_auth(self, mock_request): - mock_request.side_effect = requests.exceptions.HTTPError("404 Client Error") + @patch("pyprediktormapclient.opc_ua.request_from_api") + def test_write_historical_values_http_error_without_auth( + self, mock_request + ): + mock_request.side_effect = requests.exceptions.HTTPError( + "404 Client Error" + ) with pytest.raises(RuntimeError) as exc_info: self.opc.write_historical_values(list_of_write_historical_values) - assert str(exc_info.value) == "Error in write_historical_values: 404 Client Error" + assert ( + str(exc_info.value) + == "Error in write_historical_values: 404 Client Error" + ) @patch( "requests.post", @@ -973,13 +1090,12 @@ def test_write_historical_values_http_error_without_auth(self, mock_request): ) def test_write_historical_values_successful(self, mock_get): converted_data = [ - WriteHistoricalVariables(**item).model_dump() + WriteHistoricalVariables(**item).model_dump() for item in list_of_write_historical_values ] result = self.opc.write_historical_values(converted_data) assert all(row.get("WriteSuccess", False) for row in result) - @patch( "requests.post", side_effect=successful_write_historical_mocked_requests, @@ -992,9 +1108,7 @@ def test_write_wrong_order_historical_values_successful(self, mock_get): with pytest.raises(ValueError): self.opc.write_historical_values(converted_data) - @patch( - "requests.post", side_effect=empty_write_historical_mocked_requests - ) + @patch("requests.post", side_effect=empty_write_historical_mocked_requests) def test_write_historical_values_with_missing_value_and_statuscode( self, mock_get ): @@ -1005,9 +1119,7 @@ def test_write_historical_values_with_missing_value_and_statuscode( with pytest.raises(ValueError): self.opc.write_historical_values(converted_data) - @patch( - "requests.post", side_effect=no_write_mocked_historical_requests - ) + @patch("requests.post", side_effect=no_write_mocked_historical_requests) def test_get_write_historical_values_no_response(self, mock_get): converted_data = [ WriteHistoricalVariables(**item).model_dump() @@ -1145,17 +1257,17 @@ async def test_make_request_max_retries(self, mock_post): url=YarlURL("http://example.com"), method="POST", headers={}, - real_url=YarlURL("http://example.com") + real_url=YarlURL("http://example.com"), ), history=(), - status=500 + status=500, ) - + mock_post.side_effect = [error_response] * 3 - + with pytest.raises(RuntimeError) as exc_info: await self.opc._make_request("test_endpoint", {}, 3, 0) - + assert mock_post.call_count == 3 assert "Max retries reached" in str(exc_info.value) @@ -1170,36 +1282,41 @@ async def test_make_request_all_retries_fail(self, mock_sleep, mock_post): url=YarlURL("http://example.com"), method="POST", headers={}, - real_url=YarlURL("http://example.com") + real_url=YarlURL("http://example.com"), ), history=(), - status=400 + status=400, ) mock_post.return_value.__aenter__.return_value = error_response max_retries = 3 retry_delay = 0 with pytest.raises(RuntimeError, match="Max retries reached"): - await self.opc._make_request("test_endpoint", {}, max_retries, retry_delay) + await self.opc._make_request( + "test_endpoint", {}, max_retries, retry_delay + ) assert mock_post.call_count == max_retries assert mock_sleep.call_count == max_retries - 1 - @patch('aiohttp.ClientSession.post') + @patch("aiohttp.ClientSession.post") async def test_make_request_client_error(self, mock_post): mock_post.side_effect = aiohttp.ClientError("Test client error") - + with pytest.raises(RuntimeError): await self.opc._make_request("test_endpoint", {}, 1, 0) @patch("aiohttp.ClientSession.post") @patch("asyncio.sleep", return_value=None) - async def test_make_request_non_500_error_with_retry(self, mock_sleep, mock_post, mock_error_response): + async def test_make_request_non_500_error_with_retry( + self, mock_sleep, mock_post, mock_error_response + ): mock_error_response.status = 400 mock_error_response.text.return_value = "Bad Request" mock_error_response.raise_for_status.side_effect.status = 400 - mock_error_response.raise_for_status.side_effect.message = "Bad Request" - + mock_error_response.raise_for_status.side_effect.message = ( + "Bad Request" + ) success_response = AsyncMock() success_response.status = 200 @@ -1208,14 +1325,14 @@ async def test_make_request_non_500_error_with_retry(self, mock_sleep, mock_post mock_post.side_effect = [ AsyncMock(__aenter__=AsyncMock(return_value=mock_error_response)), AsyncMock(__aenter__=AsyncMock(return_value=mock_error_response)), - AsyncMock(__aenter__=AsyncMock(return_value=success_response)) + AsyncMock(__aenter__=AsyncMock(return_value=success_response)), ] result = await self.opc._make_request("test_endpoint", {}, 3, 0) assert result == {"success": True} assert mock_post.call_count == 3 - assert mock_sleep.call_count == 2 + assert mock_sleep.call_count == 2 mock_error_response.text.assert_awaited() mock_error_response.raise_for_status.assert_called() @@ -1264,21 +1381,27 @@ async def test_get_historical_values(self, mock_make_request): { "NodeId": {"Id": "SOMEID", "Namespace": 1, "IdType": 2}, "DataValues": [ - {"Value": {"Type": 11, "Body": 1.23}, "SourceTimestamp": "2023-01-01T00:00:00Z"} - ] + { + "Value": {"Type": 11, "Body": 1.23}, + "SourceTimestamp": "2023-01-01T00:00:00Z", + } + ], } - ] + ], } - + start_time = datetime(2023, 1, 1) end_time = datetime(2023, 1, 2) variable_list = ["SOMEID"] - + result = await self.opc.get_historical_values( - start_time, end_time, variable_list, "test_endpoint", - lambda vars: [{"NodeId": var} for var in vars] + start_time, + end_time, + variable_list, + "test_endpoint", + lambda vars: [{"NodeId": var} for var in vars], ) - + assert isinstance(result, pd.DataFrame) assert len(result) == 1 assert result.iloc[0]["Value.Body"] == 1.23 @@ -1286,7 +1409,8 @@ async def test_get_historical_values(self, mock_make_request): @patch("aiohttp.ClientSession.post") async def test_get_historical_values_no_results(self, mock_post): mock_post.return_value = AsyncMockResponse( - json_data={"Success": True, "HistoryReadResults": []}, status_code=200 + json_data={"Success": True, "HistoryReadResults": []}, + status_code=200, ) result = await self.opc.get_historical_values( @@ -1294,7 +1418,7 @@ async def test_get_historical_values_no_results(self, mock_post): end_time=datetime(2023, 1, 2), variable_list=["SOMEID"], endpoint="values/historical", - prepare_variables=lambda vars: [{"NodeId": var} for var in vars] + prepare_variables=lambda vars: [{"NodeId": var} for var in vars], ) assert result.empty @@ -1310,7 +1434,7 @@ async def test_get_raw_historical_values_asyn(self, mock_post): end_time=datetime(2023, 1, 2), variable_list=["SOMEID"], limit_start_index=0, - limit_num_records=100 + limit_num_records=100, ) assert isinstance(result, pd.DataFrame) assert "Value" in result.columns @@ -1318,33 +1442,47 @@ async def test_get_raw_historical_values_asyn(self, mock_post): async def test_get_raw_historical_values_success(self): mock_result = AsyncMock() - - with patch.object(self.opc, 'get_raw_historical_values_asyn', return_value=mock_result): - with patch.object(self.opc.helper, 'run_coroutine', return_value=mock_result) as mock_run_coroutine: + + with patch.object( + self.opc, + "get_raw_historical_values_asyn", + return_value=mock_result, + ): + with patch.object( + self.opc.helper, "run_coroutine", return_value=mock_result + ) as mock_run_coroutine: result = self.opc.get_raw_historical_values( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), variable_list=list_of_ids, ) - + mock_run_coroutine.assert_called_once() assert result == mock_result async def test_get_raw_historical_values_with_args(self): mock_result = AsyncMock() - - with patch.object(self.opc, 'get_raw_historical_values_asyn', return_value=mock_result) as mock_async: + + with patch.object( + self.opc, + "get_raw_historical_values_asyn", + return_value=mock_result, + ) as mock_async: result = await make_raw_historical_request(self.opc) - + mock_async.assert_called_once() args, kwargs = mock_async.call_args - assert 'start_time' in kwargs - assert 'end_time' in kwargs - assert kwargs['variable_list'] == list_of_ids + assert "start_time" in kwargs + assert "end_time" in kwargs + assert kwargs["variable_list"] == list_of_ids assert result == mock_result async def test_get_raw_historical_values_exception(self): - with patch.object(self.opc, 'get_raw_historical_values_asyn', side_effect=Exception("Test exception")): + with patch.object( + self.opc, + "get_raw_historical_values_asyn", + side_effect=Exception("Test exception"), + ): with pytest.raises(Exception, match="Test exception"): await make_raw_historical_request(self.opc) @@ -1358,7 +1496,7 @@ async def test_get_historical_aggregated_values_asyn(self, mock_post): end_time=datetime(2023, 1, 2), pro_interval=3600000, agg_name="Average", - variable_list=["SOMEID"] + variable_list=["SOMEID"], ) assert isinstance(result, pd.DataFrame) assert "Value" in result.columns @@ -1367,9 +1505,15 @@ async def test_get_historical_aggregated_values_asyn(self, mock_post): async def test_get_historical_aggregated_values_success(self): mock_result = AsyncMock() - - with patch.object(self.opc, 'get_historical_aggregated_values_asyn', return_value=mock_result): - with patch.object(self.opc.helper, 'run_coroutine', return_value=mock_result) as mock_run_coroutine: + + with patch.object( + self.opc, + "get_historical_aggregated_values_asyn", + return_value=mock_result, + ): + with patch.object( + self.opc.helper, "run_coroutine", return_value=mock_result + ) as mock_run_coroutine: result = self.opc.get_historical_aggregated_values( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), @@ -1377,14 +1521,18 @@ async def test_get_historical_aggregated_values_success(self): agg_name="Average", variable_list=list_of_ids, ) - + mock_run_coroutine.assert_called_once() assert result == mock_result async def test_get_historical_aggregated_values_with_args(self): mock_result = AsyncMock() - - with patch.object(self.opc, 'get_historical_aggregated_values_asyn', return_value=mock_result) as mock_async: + + with patch.object( + self.opc, + "get_historical_aggregated_values_asyn", + return_value=mock_result, + ) as mock_async: result = await self.opc.get_historical_aggregated_values_asyn( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), @@ -1392,19 +1540,27 @@ async def test_get_historical_aggregated_values_with_args(self): agg_name="Average", variable_list=list_of_ids, ) - + mock_async.assert_called_once() args, kwargs = mock_async.call_args - assert 'start_time' in kwargs - assert 'end_time' in kwargs - assert kwargs['pro_interval'] == 3600000 - assert kwargs['agg_name'] == "Average" - assert kwargs['variable_list'] == list_of_ids + assert "start_time" in kwargs + assert "end_time" in kwargs + assert kwargs["pro_interval"] == 3600000 + assert kwargs["agg_name"] == "Average" + assert kwargs["variable_list"] == list_of_ids assert result == mock_result async def test_get_historical_aggregated_values_exception(self): - with patch.object(self.opc, 'get_historical_aggregated_values_asyn', side_effect=Exception("Test exception")): - with patch.object(self.opc.helper, 'run_coroutine', side_effect=Exception("Test exception")): + with patch.object( + self.opc, + "get_historical_aggregated_values_asyn", + side_effect=Exception("Test exception"), + ): + with patch.object( + self.opc.helper, + "run_coroutine", + side_effect=Exception("Test exception"), + ): with pytest.raises(Exception, match="Test exception"): self.opc.get_historical_aggregated_values( start_time=(datetime.now() - timedelta(30)), From 43088702181d4417bc569dc5a4549b5e02d183f2 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Thu, 26 Sep 2024 11:54:14 +0200 Subject: [PATCH 22/35] Updated notebooks to read environment variables from .env file. --- notebooks/Example_Data_Downloading.ipynb | 80 ++--- notebooks/Exploring_API_Functions.ipynb | 64 ++-- ...ploring_API_Functions_Authentication.ipynb | 20 +- .../model_index_sparql_integration.ipynb | 301 ------------------ tests/dwh/db_test.py | 3 +- tests/opc_ua_test.py | 1 - 6 files changed, 70 insertions(+), 399 deletions(-) delete mode 100644 notebooks/model_index_sparql_integration.ipynb diff --git a/notebooks/Example_Data_Downloading.ipynb b/notebooks/Example_Data_Downloading.ipynb index 6441fcb..63a9178 100644 --- a/notebooks/Example_Data_Downloading.ipynb +++ b/notebooks/Example_Data_Downloading.ipynb @@ -18,19 +18,15 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Import the required packeages\n", - "import pandas as pd\n", - "from typing import List, Dict\n", - "import sys\n", "import os\n", - "import json\n", - "import asyncio\n", "import datetime\n", - "from aiohttp import ClientSession" + "from dotenv import load_dotenv \n", + "from pathlib import Path" ] }, { @@ -43,15 +39,15 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "# Setting the path\n", - "module_path = os.path.abspath(os.path.join(\"../src/pyprediktormapclient/\"))\n", - "print(module_path)\n", - "if module_path not in sys.path:\n", - " sys.path.append(module_path)" + "from pyprediktormapclient.opc_ua import OPC_UA\n", + "from pyprediktormapclient.model_index import ModelIndex\n", + "from pyprediktormapclient.auth_client import AUTH_CLIENT\n", + "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", + "from pyprediktormapclient.shared import *" ] }, { @@ -60,28 +56,31 @@ "metadata": {}, "outputs": [], "source": [ - "# Import model index functions\n", - "from pyprediktormapclient.model_index import ModelIndex\n", - "\n", - "# Import OPC UA functions\n", - "from pyprediktormapclient.opc_ua import OPC_UA\n", - "\n", - "# Import Analytics Helper\n", - "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", - "\n", - "# Import \"Dataframer\" Tools\n", - "from pyprediktormapclient.shared import *" + "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", + "dotenv_path = Path(\"../.env\")\n", + "load_dotenv(dotenv_path=dotenv_path)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "username = os.environ[\"USERNAME\"]\n", + "password = os.environ[\"PASSWORD\"]\n", + "opcua_rest_url = os.environ[\"OPC_UA_REST_URL\"]\n", + "opcua_server_url = os.environ[\"OPC_UA_SERVER_URL\"]\n", + "model_index_url = os.environ[\"MODEL_INDEX_URL\"]\n", + "ory_url = os.environ[\"ORY_URL\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "# Connection to the servers\n", - "model_index_url = \"https://modelindex.frontend.dev.powerview.io/v1/\"\n", - "\n", "# Model index API\n", "model = ModelIndex(url=model_index_url)" ] @@ -129,14 +128,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Initate the OPC UA connection\n", - "opcua_rest_url = \"https://apis-opcua-api.frontend.dev.powerview.io/\"\n", - "opcua_server_url = \"opc.tcp://UIDEV-W2022-04.prediktor.no:4860\"\n", - "\n", "tsdata = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=sites.namespaces_as_list(model.get_namespace_array()))" ] }, @@ -162,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -218,24 +214,6 @@ "one_day_historic_inverter_data" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical inverter data in asyncio process\n", - "one_days_historic_inverter_data2 = await tsdata.get_historical_aggregated_values_async(\n", - " start_time=(datetime.datetime.now() - datetime.timedelta(30)),\n", - " end_time=(datetime.datetime.now() - datetime.timedelta(29)),\n", - " pro_interval=60*1000,\n", - " agg_name=\"Average\",\n", - " variable_list=inverters.variables_as_list(inv_var_list),\n", - " batch_size=500\n", - ")\n", - "one_days_historic_inverter_data2" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/Exploring_API_Functions.ipynb b/notebooks/Exploring_API_Functions.ipynb index 7b9c8a5..4cd3ca7 100644 --- a/notebooks/Exploring_API_Functions.ipynb +++ b/notebooks/Exploring_API_Functions.ipynb @@ -18,16 +18,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Import the required packeages\n", - "import pandas as pd\n", - "from typing import List, Dict\n", - "import sys\n", "import os\n", - "import asyncio\n", + "from dotenv import load_dotenv \n", + "from pathlib import Path\n", "import datetime" ] }, @@ -41,16 +39,15 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "# Setting the path is only needed if you are running this locally from source.\n", - "# Consider using pip to install instead.\n", - "module_path = os.path.abspath(os.path.join(\"../src/pyprediktormapclient/\"))\n", - "print(module_path)\n", - "if module_path not in sys.path:\n", - " sys.path.append(module_path)" + "from pyprediktormapclient.opc_ua import OPC_UA\n", + "from pyprediktormapclient.model_index import ModelIndex\n", + "from pyprediktormapclient.auth_client import AUTH_CLIENT\n", + "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", + "from pyprediktormapclient.shared import *" ] }, { @@ -59,28 +56,31 @@ "metadata": {}, "outputs": [], "source": [ - "# Import model index functions\n", - "from pyprediktormapclient.model_index import ModelIndex\n", - "\n", - "# Import OPC UA functions\n", - "from pyprediktormapclient.opc_ua import OPC_UA\n", - "\n", - "# Import Analytics Helper\n", - "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", - "\n", - "# Import \"Dataframer\" Tools\n", - "from pyprediktormapclient.shared import *" + "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", + "dotenv_path = Path(\"../.env\")\n", + "load_dotenv(dotenv_path=dotenv_path)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "username = os.environ[\"USERNAME\"]\n", + "password = os.environ[\"PASSWORD\"]\n", + "opcua_rest_url = os.environ[\"OPC_UA_REST_URL\"]\n", + "opcua_server_url = os.environ[\"OPC_UA_SERVER_URL\"]\n", + "model_index_url = os.environ[\"MODEL_INDEX_URL\"]\n", + "ory_url = os.environ[\"ORY_URL\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "# Connection to the servers\n", - "model_index_url = \"http://uidev-ubuntu-02.prediktor.no:13701/v1/\"\n", - "\n", "# Model index API\n", "model = ModelIndex(url=model_index_url)" ] @@ -118,16 +118,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "namespace_list = object_types.namespaces_as_list(namespaces)\n", "\n", - "# Connection to the servers\n", - "opcua_rest_url = \"http://uidev-ubuntu-02.prediktor.no:13702/\"\n", - "opcua_server_url = \"opc.tcp://10.100.59.154:4860\"\n", - "\n", "# Initate the OPC UA API with a fixed namespace list\n", "tsdata = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=namespace_list)" ] @@ -243,7 +239,7 @@ "source": [ "# Live value data of trackers\n", "live_value = tsdata.get_values(\n", - " trackers.variables_as_list([\"AngleMeasured\", \"AngleSetpoint\"])\n", + " trackers.variables_as_list([\"AngleMeasured\"])\n", ")\n", "live_value" ] @@ -282,7 +278,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.12.4" }, "orig_nbformat": 4, "vscode": { diff --git a/notebooks/Exploring_API_Functions_Authentication.ipynb b/notebooks/Exploring_API_Functions_Authentication.ipynb index 865a5a6..0e8d968 100644 --- a/notebooks/Exploring_API_Functions_Authentication.ipynb +++ b/notebooks/Exploring_API_Functions_Authentication.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -95,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -188,7 +188,7 @@ "outputs": [], "source": [ "# Selecting the single site\n", - "site_id = sites.list_of_ids()\n", + "site_id = sites.list_of_ids()[0]\n", "site_id" ] }, @@ -200,7 +200,7 @@ "source": [ "# Get all stringsets for one park\n", "string_sets_for_first_park_as_json = model_data.get_object_descendants(\n", - " \"StringSetType\", site_id, \"PV_Assets\"\n", + " \"StringSetType\", [site_id], \"PV_Assets\"\n", ")\n", "string_sets_for_first_park = AnalyticsHelper(string_sets_for_first_park_as_json)\n", "string_sets_for_first_park.dataframe" @@ -208,13 +208,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# All inverter data for the site\n", "inverter_json = model_data.get_object_descendants(\n", - " \"InverterType\", site_id, \"PV_Assets\"\n", + " \"InverterType\", [site_id], \"PV_Assets\"\n", ")\n", "inverters = AnalyticsHelper(inverter_json)" ] @@ -243,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ diff --git a/notebooks/model_index_sparql_integration.ipynb b/notebooks/model_index_sparql_integration.ipynb deleted file mode 100644 index 856b897..0000000 --- a/notebooks/model_index_sparql_integration.ipynb +++ /dev/null @@ -1,301 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import the required packages and libraries\n", - "import json\n", - "import requests\n", - "import datetime\n", - "import pandas as pd\n", - "import os\n", - "from dotenv import load_dotenv \n", - "from pathlib import Path\n", - "import nest_asyncio\n", - "\n", - "nest_asyncio.apply()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pyprediktormapclient.auth_client import AUTH_CLIENT\n", - "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", - "from pyprediktormapclient.shared import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", - "dotenv_path = Path(\"../.env\")\n", - "load_dotenv(dotenv_path=dotenv_path)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "username = os.environ[\"USERNAME\"]\n", - "password = os.environ[\"PASSWORD\"]\n", - "opcua_rest_url = os.environ[\"OPC_UA_REST_URL\"]\n", - "opcua_server_url = os.environ[\"OPC_UA_SERVER_URL\"]\n", - "model_index_url = os.environ[\"MODEL_INDEX_URL\"]\n", - "ory_url = os.environ[\"ORY_URL\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Getting ory bearer token\n", - "auth_client = AUTH_CLIENT(rest_url=ory_url, username=username, password=password)\n", - "auth_client.request_new_ory_token()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "url = model_index_url\n", - "headers = {\"Content-Type\": \"application/json\",\n", - " \"Accept\": \"application/json\"\n", - " }\n", - "auth_client = auth_client\n", - "session: requests.Session = None" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if auth_client is not None:\n", - " if auth_client.token is not None:\n", - " headers[\"Authorization\"] = f\"Bearer {auth_client.token.session_token}\"\n", - " if hasattr(auth_client, 'session_token'):\n", - " headers[\"Cookie\"] = f\"ory_kratos_session={auth_client.session_token}\"\n", - "\n", - "body = {\"Connection\": {\"Url\": url, \"AuthenticationType\": 1}}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_object_types() -> str:\n", - " content = request_from_api(url, \"GET\", \"query/object-types\", headers=headers, session=session)\n", - "\n", - " return content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "object_types = get_object_types()\n", - "object_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_namespace_array() -> str:\n", - " \"\"\"Get the namespace array\n", - "\n", - " Returns:\n", - " str: the JSON returned from the server\n", - " \"\"\"\n", - " content = request_from_api(url, \"GET\", \"query/namespaces\", headers=headers, session=session)\n", - "\n", - " return content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "name_space_array = get_namespace_array()\n", - "name_space_array" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_object_type_id_from_name(type_name: str) -> str:\n", - " \"\"\"Get object type id from type name\n", - "\n", - " Args:\n", - " type_name (str): type name\n", - "\n", - " Returns:\n", - " str: the type id that corresponds with the id (None if not found)\n", - " \"\"\"\n", - " try:\n", - " obj_type = next(\n", - " item for item in object_types if item[\"BrowseName\"][\"Name\"] == type_name\n", - " )\n", - " except StopIteration:\n", - " obj_type = {}\n", - "\n", - " if obj_type:\n", - " node_id = obj_type.get(\"NodeId\", {})\n", - " object_type_id = f'{node_id.get(\"Namespace\")}:{node_id.get(\"IdType\")}:{node_id.get(\"Id\")}'\n", - " else:\n", - " object_type_id = None\n", - "\n", - " return object_type_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "object_type_id = get_object_type_id_from_name(\"StringSetType\")\n", - "object_type_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_objects_of_type(type_name: str) -> str:\n", - " \"\"\"Function to get all the types of an object\n", - "\n", - " Args:\n", - " type_name (str): type name\n", - "\n", - " Returns:\n", - " A json-formatted string with the objects (or None if the type is not found)\n", - " \"\"\"\n", - " object_type_id = get_object_type_id_from_name(type_name)\n", - " if object_type_id is None:\n", - " return None\n", - " \n", - " namespace, id_type, id_ = object_type_id.split(':')\n", - "\n", - " body = json.dumps({\"Id\": int(id_), \"IdType\": int(id_type), \"Namespace\": int(namespace)})\n", - " content = request_from_api(url, \"POST\", \"query/objects-of-type-with-variables-and-properties\", body, headers=headers, session=session)\n", - "\n", - " return content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "objects_of_type = get_objects_of_type(\"SiteType\")\n", - "objects_of_type" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_object_descendants(\n", - " type_name: str,\n", - " ids: list,\n", - " domain: str,\n", - " ) -> str:\n", - " \"\"\"A function to get object descendants\n", - "\n", - " Args:\n", - " type_name (str): type_name of a descendant\n", - " ids (list): a list of ids you want the descendants for\n", - " domain (str): PV_Assets or PV_Serves\n", - "\n", - " Returns:\n", - " A json-formatted string with descendats data of selected object (or None if the type is not found)\n", - " \"\"\"\n", - " if type_name is None or not ids:\n", - " raise ValidationError(\"type_name and ids cannot be None or empty\")\n", - " \n", - " id = get_object_type_id_from_name(type_name)\n", - " body = json.dumps(\n", - " {\n", - " \"typeId\": id,\n", - " \"objectIds\": ids,\n", - " \"domain\": domain,\n", - " }\n", - " )\n", - " content = request_from_api(url, \"POST\", \"query/object-descendants?include_members=true\", body, headers=headers, session=session)\n", - "\n", - " return content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "object_descendants = get_object_descendants(\"StringSetType\", ['3:1:Enterprise.JO-GL'], \"PV_Assets\")\n", - "object_descendants" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv_map_client", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 2a950ec..fc224fd 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -63,7 +63,6 @@ def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): ), ], ) - def test_init_connection_error( self, monkeypatch, @@ -155,7 +154,7 @@ def compare_signatures(impl_method, abstract_method): abstract_return = abstract_sig.return_annotation impl_origin = get_origin(impl_return) or impl_return - abstract_origin = get_origin(abstract_return) or abstract_origin + abstract_origin = get_origin(abstract_return) or abstract_return assert ( impl_origin == abstract_origin diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index af964c9..cbd83ed 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1,6 +1,5 @@ import unittest from unittest.mock import patch, Mock, AsyncMock -from requests.exceptions import HTTPError import pytest from datetime import datetime, timedelta, date import aiohttp From 9ea30c96c0242e7d101c255417a887df5bdd82b5 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Thu, 26 Sep 2024 15:59:51 +0200 Subject: [PATCH 23/35] Refactored tests and added fixtures. --- notebooks/Example_Data_Downloading.ipynb | 12 +- notebooks/Exploring_API_Functions.ipynb | 10 +- ...ploring_API_Functions_Authentication.ipynb | 9381 ++++++++++++++++- notebooks/api_performance_testing.ipynb | 2188 ---- tests/analytics_helper_test.py | 154 +- tests/auth_client_test.py | 151 +- tests/conftest.py | 66 +- tests/dwh/db_test.py | 83 +- tests/dwh/dwh_test.py | 137 +- tests/model_index_test.py | 172 +- tests/opc_ua_test.py | 9 - 11 files changed, 9668 insertions(+), 2695 deletions(-) delete mode 100644 notebooks/api_performance_testing.ipynb diff --git a/notebooks/Example_Data_Downloading.ipynb b/notebooks/Example_Data_Downloading.ipynb index 63a9178..a913612 100644 --- a/notebooks/Example_Data_Downloading.ipynb +++ b/notebooks/Example_Data_Downloading.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -128,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -158,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ diff --git a/notebooks/Exploring_API_Functions.ipynb b/notebooks/Exploring_API_Functions.ipynb index 4cd3ca7..156fa61 100644 --- a/notebooks/Exploring_API_Functions.ipynb +++ b/notebooks/Exploring_API_Functions.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -118,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ diff --git a/notebooks/Exploring_API_Functions_Authentication.ipynb b/notebooks/Exploring_API_Functions_Authentication.ipynb index 0e8d968..dbbb89b 100644 --- a/notebooks/Exploring_API_Functions_Authentication.ipynb +++ b/notebooks/Exploring_API_Functions_Authentication.ipynb @@ -52,9 +52,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", "dotenv_path = Path(\"../.env\")\n", @@ -105,9 +116,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[{'Idx': 0, 'Uri': 'http://opcfoundation.org/UA/'},\n", + " {'Idx': 1, 'Uri': 'http://prediktor.no/apis/ua/'},\n", + " {'Idx': 2, 'Uri': 'urn:prediktor:UIDEV-W2022-04:Scatec'},\n", + " {'Idx': 3, 'Uri': 'http://scatecsolar.com/EG-AS'},\n", + " {'Idx': 4, 'Uri': 'http://scatecsolar.com/Enterprise'},\n", + " {'Idx': 5, 'Uri': 'http://scatecsolar.com/JO-GL'},\n", + " {'Idx': 6, 'Uri': 'http://prediktor.no/PVTypes/'},\n", + " {'Idx': 7, 'Uri': 'http://powerview.com/enterprise'}]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Listed sites on the model index api server\n", "namespaces = model_data.get_namespace_array()\n", @@ -116,9 +145,166 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdNameBrowseNamePropsVars
06:0:1052ICalcTrackerICalcTracker[][]
16:0:1061EquipmentEventTypeEquipmentEventType[][]
26:0:1128EnergyAndPowerMeterEventTypeEnergyAndPowerMeterEventType[][]
36:0:1263EnergyAndPowerMeterCommLossEventTypeEnergyAndPowerMeterCommLossEventType[][]
46:0:1266EnergyAndPowerMeterErrorEventTypeEnergyAndPowerMeterErrorEventType[][]
..................
2396:0:1050OpcUaHiveModuleOpcUaHiveModule[][]
2406:0:1057BaseHiveModuleNoItemsTypeBaseHiveModuleNoItemsType[][]
2416:0:1067LoggerHiveModuleLoggerHiveModule[][]
2426:0:1055EquipmentTemplateDefinitionTypeEquipmentTemplateDefinitionType[][]
2436:0:1066HivePropertyTypeHivePropertyType[][]
\n", + "

244 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " Id Name \\\n", + "0 6:0:1052 ICalcTracker \n", + "1 6:0:1061 EquipmentEventType \n", + "2 6:0:1128 EnergyAndPowerMeterEventType \n", + "3 6:0:1263 EnergyAndPowerMeterCommLossEventType \n", + "4 6:0:1266 EnergyAndPowerMeterErrorEventType \n", + ".. ... ... \n", + "239 6:0:1050 OpcUaHiveModule \n", + "240 6:0:1057 BaseHiveModuleNoItemsType \n", + "241 6:0:1067 LoggerHiveModule \n", + "242 6:0:1055 EquipmentTemplateDefinitionType \n", + "243 6:0:1066 HivePropertyType \n", + "\n", + " BrowseName Props Vars \n", + "0 ICalcTracker [] [] \n", + "1 EquipmentEventType [] [] \n", + "2 EnergyAndPowerMeterEventType [] [] \n", + "3 EnergyAndPowerMeterCommLossEventType [] [] \n", + "4 EnergyAndPowerMeterErrorEventType [] [] \n", + ".. ... ... ... \n", + "239 OpcUaHiveModule [] [] \n", + "240 BaseHiveModuleNoItemsType [] [] \n", + "241 LoggerHiveModule [] [] \n", + "242 EquipmentTemplateDefinitionType [] [] \n", + "243 HivePropertyType [] [] \n", + "\n", + "[244 rows x 5 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Types of Objects\n", "object_types_json = model_data.get_object_types()\n", @@ -128,9 +314,117 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdName
06:0:1052ICalcTracker
16:0:1061EquipmentEventType
26:0:1128EnergyAndPowerMeterEventType
36:0:1263EnergyAndPowerMeterCommLossEventType
46:0:1266EnergyAndPowerMeterErrorEventType
.........
1186:0:1050OpcUaHiveModule
1196:0:1057BaseHiveModuleNoItemsType
1206:0:1067LoggerHiveModule
1216:0:1055EquipmentTemplateDefinitionType
1226:0:1066HivePropertyType
\n", + "

122 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " Id Name\n", + "0 6:0:1052 ICalcTracker\n", + "1 6:0:1061 EquipmentEventType\n", + "2 6:0:1128 EnergyAndPowerMeterEventType\n", + "3 6:0:1263 EnergyAndPowerMeterCommLossEventType\n", + "4 6:0:1266 EnergyAndPowerMeterErrorEventType\n", + ".. ... ...\n", + "118 6:0:1050 OpcUaHiveModule\n", + "119 6:0:1057 BaseHiveModuleNoItemsType\n", + "120 6:0:1067 LoggerHiveModule\n", + "121 6:0:1055 EquipmentTemplateDefinitionType\n", + "122 6:0:1066 HivePropertyType\n", + "\n", + "[122 rows x 2 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Unique types of Objects\n", "object_types_unique = object_types.dataframe[[\"Id\", \"Name\"]].drop_duplicates()\n", @@ -139,9 +433,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'6:0:1009'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# To get typeId by type name of an object\n", "object_type_id = model_data.get_object_type_id_from_name(\"SiteType\")\n", @@ -150,9 +455,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "['EG-AS', 'JO-GL']" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# To get the objects of a type\n", "sites_json = model_data.get_objects_of_type(\"SiteType\")\n", @@ -164,9 +480,204 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdTypeNameVariableIdVariableNameVariableIdSplit
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Alarms.CommLossPlantDeviceCommLossPlantDevice{'Id': 'Enterprise.EG-AS.Alarms.CommLossPlantD...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Signals.PPC.IsCurtailmentPPC.IsCurtailment{'Id': 'Enterprise.EG-AS.Signals.PPC.IsCurtail...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Signals.State.IsDayState.IsDay{'Id': 'Enterprise.EG-AS.Signals.State.IsDay',...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Parameters.ContractDurationContractDuration{'Id': 'Enterprise.EG-AS.Parameters.ContractDu...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Parameters.RegionKeyRegionKey{'Id': 'Enterprise.EG-AS.Parameters.RegionKey'...
.....................
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationDiffuseHorizontal{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationHorizontal{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationInCline{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.ACActiveEnergy.TotalACActiveEnergy.Total{'Id': 'Enterprise.JO-GL.Signals.ACActiveEnerg...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.StatusStatus{'Id': 'Enterprise.JO-GL.Signals.Status', 'Nam...
\n", + "

232 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " Id Type Name \\\n", + "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", + "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", + "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", + "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", + "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", + ".. ... ... ... \n", + "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", + "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", + "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", + "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", + "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", + "\n", + " VariableId \\\n", + "0 3:1:Enterprise.EG-AS.Alarms.CommLossPlantDevice \n", + "0 3:1:Enterprise.EG-AS.Signals.PPC.IsCurtailment \n", + "0 3:1:Enterprise.EG-AS.Signals.State.IsDay \n", + "0 3:1:Enterprise.EG-AS.Parameters.ContractDuration \n", + "0 3:1:Enterprise.EG-AS.Parameters.RegionKey \n", + ".. ... \n", + "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", + "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", + "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", + "1 5:1:Enterprise.JO-GL.Signals.ACActiveEnergy.Total \n", + "1 5:1:Enterprise.JO-GL.Signals.Status \n", + "\n", + " VariableName \\\n", + "0 CommLossPlantDevice \n", + "0 PPC.IsCurtailment \n", + "0 State.IsDay \n", + "0 ContractDuration \n", + "0 RegionKey \n", + ".. ... \n", + "1 Weather.IrradiationDiffuseHorizontal \n", + "1 Weather.IrradiationHorizontal \n", + "1 Weather.IrradiationInCline \n", + "1 ACActiveEnergy.Total \n", + "1 Status \n", + "\n", + " VariableIdSplit \n", + "0 {'Id': 'Enterprise.EG-AS.Alarms.CommLossPlantD... \n", + "0 {'Id': 'Enterprise.EG-AS.Signals.PPC.IsCurtail... \n", + "0 {'Id': 'Enterprise.EG-AS.Signals.State.IsDay',... \n", + "0 {'Id': 'Enterprise.EG-AS.Parameters.ContractDu... \n", + "0 {'Id': 'Enterprise.EG-AS.Parameters.RegionKey'... \n", + ".. ... \n", + "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", + "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", + "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", + "1 {'Id': 'Enterprise.JO-GL.Signals.ACActiveEnerg... \n", + "1 {'Id': 'Enterprise.JO-GL.Signals.Status', 'Nam... \n", + "\n", + "[232 rows x 6 columns]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Analytics helper\n", "sites.variables_as_dataframe()" @@ -174,18 +685,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "['3:1:Enterprise.EG-AS', '5:1:Enterprise.JO-GL']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sites.list_of_ids()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'3:1:Enterprise.EG-AS'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Selecting the single site\n", "site_id = sites.list_of_ids()[0]\n", @@ -194,9 +727,179 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdNameTypePropsVars
03:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01EG-AS-TS01-I01-SM01-CH01StringSetType[{'DisplayName': 'ChannelNo', 'Value': '01'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
13:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH02EG-AS-TS01-I01-SM01-CH02StringSetType[{'DisplayName': 'ChannelNo', 'Value': '02'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
23:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH03EG-AS-TS01-I01-SM01-CH03StringSetType[{'DisplayName': 'ChannelNo', 'Value': '03'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
33:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH04EG-AS-TS01-I01-SM01-CH04StringSetType[{'DisplayName': 'ChannelNo', 'Value': '04'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
43:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH05EG-AS-TS01-I01-SM01-CH05StringSetType[{'DisplayName': 'ChannelNo', 'Value': '05'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
..................
29333:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH05EG-AS-TS11-I22-SM13-CH05StringSetType[{'DisplayName': 'ChannelNo', 'Value': '05'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29343:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH06EG-AS-TS11-I22-SM13-CH06StringSetType[{'DisplayName': 'ChannelNo', 'Value': '06'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29353:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH07EG-AS-TS11-I22-SM13-CH07StringSetType[{'DisplayName': 'ChannelNo', 'Value': '07'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29363:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH08EG-AS-TS11-I22-SM13-CH08StringSetType[{'DisplayName': 'ChannelNo', 'Value': '08'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29373:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09EG-AS-TS11-I22-SM13-CH09StringSetType[{'DisplayName': 'ChannelNo', 'Value': '09'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
\n", + "

2938 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " Id Name \\\n", + "0 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01 EG-AS-TS01-I01-SM01-CH01 \n", + "1 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH02 EG-AS-TS01-I01-SM01-CH02 \n", + "2 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH03 EG-AS-TS01-I01-SM01-CH03 \n", + "3 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH04 EG-AS-TS01-I01-SM01-CH04 \n", + "4 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH05 EG-AS-TS01-I01-SM01-CH05 \n", + "... ... ... \n", + "2933 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH05 EG-AS-TS11-I22-SM13-CH05 \n", + "2934 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH06 EG-AS-TS11-I22-SM13-CH06 \n", + "2935 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH07 EG-AS-TS11-I22-SM13-CH07 \n", + "2936 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH08 EG-AS-TS11-I22-SM13-CH08 \n", + "2937 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09 EG-AS-TS11-I22-SM13-CH09 \n", + "\n", + " Type Props \\\n", + "0 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '01'}, ... \n", + "1 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '02'}, ... \n", + "2 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '03'}, ... \n", + "3 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '04'}, ... \n", + "4 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '05'}, ... \n", + "... ... ... \n", + "2933 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '05'}, ... \n", + "2934 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '06'}, ... \n", + "2935 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '07'}, ... \n", + "2936 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '08'}, ... \n", + "2937 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '09'}, ... \n", + "\n", + " Vars \n", + "0 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "1 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "2 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "3 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "4 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "... ... \n", + "2933 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "2934 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "2935 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "2936 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "2937 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", + "\n", + "[2938 rows x 5 columns]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Get all stringsets for one park\n", "string_sets_for_first_park_as_json = model_data.get_object_descendants(\n", @@ -208,7 +911,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -221,9 +924,204 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdNameTypeVariableIdVariableNameVariableIdSplit
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....TrackerOutOfPos{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasHighSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasMediumSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasLowSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....CommLoss{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
.....................
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....TrackingLimitWestAngle{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....MotorPressure{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....Category{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....StateCode{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....Status{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
\n", + "

111568 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " Id Name \\\n", + "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", + "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", + "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", + "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", + "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", + "... ... ... \n", + "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", + "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", + "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", + "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", + "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", + "\n", + " Type VariableId \\\n", + "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", + "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", + "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", + "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", + "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", + "... ... ... \n", + "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", + "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", + "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", + "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", + "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", + "\n", + " VariableName \\\n", + "0 TrackerOutOfPos \n", + "0 State.HasHighSeverityAlarm \n", + "0 State.HasMediumSeverityAlarm \n", + "0 State.HasLowSeverityAlarm \n", + "0 CommLoss \n", + "... ... \n", + "5871 TrackingLimitWestAngle \n", + "5871 MotorPressure \n", + "5871 Category \n", + "5871 StateCode \n", + "5871 Status \n", + "\n", + " VariableIdSplit \n", + "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", + "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", + "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", + "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", + "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", + "... ... \n", + "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", + "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", + "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", + "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", + "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", + "\n", + "[111568 rows x 6 columns]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Ancestors of an object type, get all trackers that are ancestor of the parks string sets\n", "\n", @@ -243,7 +1141,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -255,9 +1153,8020 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR002.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334401Z',\n", + " 'Value': 20.00982,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR004.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7284264Z',\n", + " 'Value': 39.937416,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR003.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334346Z',\n", + " 'Value': 39.95844,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR005.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7114424Z',\n", + " 'Value': 40.297165,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR006.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334283Z',\n", + " 'Value': 39.893333,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR008.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7184438Z',\n", + " 'Value': 38.764885,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR007.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7265985Z',\n", + " 'Value': 39.426506,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR009.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154025Z',\n", + " 'Value': 38.417027,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR010.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334392Z',\n", + " 'Value': 39.488316,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR011.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7284229Z',\n", + " 'Value': 39.20795,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR012.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6950084Z',\n", + " 'Value': 39.74066,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR014.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004301Z',\n", + " 'Value': 39.336094,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR013.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334246Z',\n", + " 'Value': 39.869442,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR015.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733442Z',\n", + " 'Value': 38.648,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR016.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334327Z',\n", + " 'Value': 38.795746,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR018.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334378Z',\n", + " 'Value': 40.091286,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR017.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334425Z',\n", + " 'Value': 40.164062,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR019.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334429Z',\n", + " 'Value': 38.7463,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR020.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254122Z',\n", + " 'Value': 38.563805,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR021.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334431Z',\n", + " 'Value': 40.117725,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR022.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334432Z',\n", + " 'Value': 40.0371,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR024.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014236Z',\n", + " 'Value': 40.117725,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR023.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334233Z',\n", + " 'Value': 40.02778,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR025.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004288Z',\n", + " 'Value': 39.271294,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR026.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7284452Z',\n", + " 'Value': 40.09894,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR028.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314134Z',\n", + " 'Value': 40.02778,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR027.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334435Z',\n", + " 'Value': 39.348305,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR029.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974027Z',\n", + " 'Value': 38.480316,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR030.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314389Z',\n", + " 'Value': 39.18548,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR031.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334182Z',\n", + " 'Value': 39.961735,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR032.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7266032Z',\n", + " 'Value': 39.271294,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR034.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314072Z',\n", + " 'Value': 39.968727,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR033.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334437Z',\n", + " 'Value': 38.628315,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR035.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334439Z',\n", + " 'Value': 38.821533,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR036.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334441Z',\n", + " 'Value': 39.348305,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR038.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334443Z',\n", + " 'Value': 39.448994,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR037.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7034534Z',\n", + " 'Value': 38.359806,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR039.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334444Z',\n", + " 'Value': 38.813255,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR040.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334304Z',\n", + " 'Value': 38.480316,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR071.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294087Z',\n", + " 'Value': 39.088306,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR072.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314439Z',\n", + " 'Value': 39.82448,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR074.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214305Z',\n", + " 'Value': 40.183846,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR073.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334475Z',\n", + " 'Value': 39.10047,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR075.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014177Z',\n", + " 'Value': 39.293728,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR076.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974381Z',\n", + " 'Value': 38.941372,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR078.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124285Z',\n", + " 'Value': 38.92468,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR077.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334416Z',\n", + " 'Value': 39.59775,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR079.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334482Z',\n", + " 'Value': 39.369774,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR080.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7174464Z',\n", + " 'Value': 39.944653,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR061.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7104211Z',\n", + " 'Value': 39.68624,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR062.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994225Z',\n", + " 'Value': 39.448288,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR064.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334469Z',\n", + " 'Value': 39.76726,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR063.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334464Z',\n", + " 'Value': 39.7911,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR065.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963584Z',\n", + " 'Value': 38.4654,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR066.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910524Z',\n", + " 'Value': 38.101276,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR068.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334472Z',\n", + " 'Value': 39.444527,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR067.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920386Z',\n", + " 'Value': 39.58335,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR069.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334474Z',\n", + " 'Value': 38.274776,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR070.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7074495Z',\n", + " 'Value': 38.525463,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR051.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334454Z',\n", + " 'Value': 39.135864,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR052.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334455Z',\n", + " 'Value': 39.74165,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR054.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910495Z',\n", + " 'Value': 38.86965,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR053.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154267Z',\n", + " 'Value': 39.895676,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR055.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324273Z',\n", + " 'Value': 39.636486,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR056.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7304351Z',\n", + " 'Value': 38.616066,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR058.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733446Z',\n", + " 'Value': 39.966393,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR057.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324436Z',\n", + " 'Value': 39.798927,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR059.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.707413Z',\n", + " 'Value': 39.86294,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR060.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214557Z',\n", + " 'Value': 38.576267,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR041.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7164483Z',\n", + " 'Value': 39.77297,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR042.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244401Z',\n", + " 'Value': 38.479317,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR044.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7194263Z',\n", + " 'Value': 39.138203,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR043.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7234174Z',\n", + " 'Value': 39.410217,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR045.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334448Z',\n", + " 'Value': 38.256565,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR046.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974214Z',\n", + " 'Value': 39.57831,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR048.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334373Z',\n", + " 'Value': 38.546345,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR047.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054104Z',\n", + " 'Value': 40.17925,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR049.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7174425Z',\n", + " 'Value': 40.15407,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR050.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244407Z',\n", + " 'Value': 39.477345,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR081.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334316Z',\n", + " 'Value': 38.30171,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR082.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254206Z',\n", + " 'Value': 39.335415,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR084.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334484Z',\n", + " 'Value': 39.482883,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR083.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.694009Z',\n", + " 'Value': 39.734528,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR085.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334485Z',\n", + " 'Value': 38.66229,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR086.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.707424Z',\n", + " 'Value': 40.226,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR088.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334489Z',\n", + " 'Value': 38.165596,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR087.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7104158Z',\n", + " 'Value': 39.07549,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR089.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733449Z',\n", + " 'Value': 39.488422,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR090.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.720428Z',\n", + " 'Value': 38.802856,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR091.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.695007Z',\n", + " 'Value': 38.416016,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR092.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334492Z',\n", + " 'Value': 39.11174,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR094.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334166Z',\n", + " 'Value': 39.698303,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR093.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334397Z',\n", + " 'Value': 39.56181,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR095.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334494Z',\n", + " 'Value': 39.41977,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR096.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334496Z',\n", + " 'Value': 39.00415,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR098.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054515Z',\n", + " 'Value': 38.210358,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR097.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334498Z',\n", + " 'Value': 39.15221,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR099.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214108Z',\n", + " 'Value': 40.00306,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR100.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334253Z',\n", + " 'Value': 38.696022,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR101.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334501Z',\n", + " 'Value': 38.7922,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR102.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334468Z',\n", + " 'Value': 38.982304,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR104.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334504Z',\n", + " 'Value': 39.395348,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR103.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214084Z',\n", + " 'Value': 38.24809,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR105.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334505Z',\n", + " 'Value': 38.646324,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR106.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124062Z',\n", + " 'Value': 39.640224,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR108.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334508Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR107.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7184488Z',\n", + " 'Value': 39.834618,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR109.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6964056Z',\n", + " 'Value': 38.23865,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR110.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6983988Z',\n", + " 'Value': 38.86368,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR111.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054354Z',\n", + " 'Value': 39.45928,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR112.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334511Z',\n", + " 'Value': 39.847073,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR114.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7194315Z',\n", + " 'Value': 38.547565,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR113.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7063999Z',\n", + " 'Value': 39.246708,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR115.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7271216Z',\n", + " 'Value': 38.964947,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR116.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7234039Z',\n", + " 'Value': 39.932182,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR118.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974451Z',\n", + " 'Value': 40.094357,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR117.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334097Z',\n", + " 'Value': 39.842464,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR119.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910345Z',\n", + " 'Value': 39.845444,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR120.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334515Z',\n", + " 'Value': 39.96943,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR131.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334512Z',\n", + " 'Value': 39.403713,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR132.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334527Z',\n", + " 'Value': 40.1329,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR133.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974285Z',\n", + " 'Value': 38.44644,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR134.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334421Z',\n", + " 'Value': 39.736664,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR135.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7024352Z',\n", + " 'Value': 39.254227,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR136.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7094278Z',\n", + " 'Value': 38.39941,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR137.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334276Z',\n", + " 'Value': 38.8705,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR138.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334231Z',\n", + " 'Value': 39.11725,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR139.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334122Z',\n", + " 'Value': 39.085377,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR140.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334532Z',\n", + " 'Value': 38.234764,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR121.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6960057Z',\n", + " 'Value': 39.273518,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR122.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910312Z',\n", + " 'Value': 39.378483,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR123.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733452Z',\n", + " 'Value': 38.45839,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR124.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334446Z',\n", + " 'Value': 38.27804,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR125.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334521Z',\n", + " 'Value': 40.069504,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR126.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334288Z',\n", + " 'Value': 39.804817,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR127.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334522Z',\n", + " 'Value': 38.57075,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR128.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334523Z',\n", + " 'Value': 38.319588,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR129.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.73343Z',\n", + " 'Value': 39.81896,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR130.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334526Z',\n", + " 'Value': 39.566437,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR242.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940364Z',\n", + " 'Value': 39.44667,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR241.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054231Z',\n", + " 'Value': 40.021214,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR243.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344058Z',\n", + " 'Value': 38.65197,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR244.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.721432Z',\n", + " 'Value': 39.945236,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR246.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7164425Z',\n", + " 'Value': 39.52573,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR245.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7224288Z',\n", + " 'Value': 39.7197,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR247.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344062Z',\n", + " 'Value': 39.13543,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR248.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344066Z',\n", + " 'Value': 38.94027,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR250.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734407Z',\n", + " 'Value': 39.773697,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR249.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344068Z',\n", + " 'Value': 39.559357,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR252.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7034049Z',\n", + " 'Value': 39.39085,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR251.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344074Z',\n", + " 'Value': 39.544086,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR253.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.731408Z',\n", + " 'Value': 39.41263,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR254.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344079Z',\n", + " 'Value': 38.13906,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR256.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734408Z',\n", + " 'Value': 38.6691,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR255.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.71841Z',\n", + " 'Value': 40.068417,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR257.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294025Z',\n", + " 'Value': 38.549046,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR258.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344089Z',\n", + " 'Value': 39.916183,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR260.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344093Z',\n", + " 'Value': 38.91388,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR259.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014037Z',\n", + " 'Value': 39.523148,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR262.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344096Z',\n", + " 'Value': 38.717606,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR261.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7194121Z',\n", + " 'Value': 39.89171,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR263.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014241Z',\n", + " 'Value': 38.91663,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR264.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920102Z',\n", + " 'Value': 39.07026,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR266.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344103Z',\n", + " 'Value': 40.11856,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR265.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6950211Z',\n", + " 'Value': 39.17636,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR267.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004098Z',\n", + " 'Value': 39.230713,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR268.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344105Z',\n", + " 'Value': 38.88261,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR270.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344107Z',\n", + " 'Value': 39.035175,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR269.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910499Z',\n", + " 'Value': 39.870422,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR272.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7174237Z',\n", + " 'Value': 39.945263,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR271.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154412Z',\n", + " 'Value': 39.57766,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR273.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.725426Z',\n", + " 'Value': 40.050755,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR274.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734411Z',\n", + " 'Value': 39.593277,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR276.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994301Z',\n", + " 'Value': 38.47257,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR275.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314065Z',\n", + " 'Value': 39.58106,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR277.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214234Z',\n", + " 'Value': 40.277138,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR278.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294077Z',\n", + " 'Value': 40.155914,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR280.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344118Z',\n", + " 'Value': 38.441658,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR279.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344116Z',\n", + " 'Value': 38.672085,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR232.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344031Z',\n", + " 'Value': 38.39525,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR231.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940069Z',\n", + " 'Value': 39.895023,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR233.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344032Z',\n", + " 'Value': 38.925884,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR234.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324109Z',\n", + " 'Value': 38.453194,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR236.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344045Z',\n", + " 'Value': 39.69611,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR235.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344038Z',\n", + " 'Value': 38.970055,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR237.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344048Z',\n", + " 'Value': 38.83117,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR238.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734405Z',\n", + " 'Value': 39.499306,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR240.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344054Z',\n", + " 'Value': 39.711494,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR239.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273885Z',\n", + " 'Value': 39.04134,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR222.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134056Z',\n", + " 'Value': 39.314827,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR221.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334333Z',\n", + " 'Value': 38.66296,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR223.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733447Z',\n", + " 'Value': 39.55961,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR224.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7044362Z',\n", + " 'Value': 39.352505,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR226.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334458Z',\n", + " 'Value': 39.022774,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR225.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334463Z',\n", + " 'Value': 39.615925,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR227.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334461Z',\n", + " 'Value': 39.504097,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR228.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734403Z',\n", + " 'Value': 40.013596,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR230.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930048Z',\n", + " 'Value': 38.451878,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR229.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334438Z',\n", + " 'Value': 39.184235,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR212.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7064038Z',\n", + " 'Value': 39.886215,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR211.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134145Z',\n", + " 'Value': 40.061974,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR213.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214083Z',\n", + " 'Value': 39.39644,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR214.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334326Z',\n", + " 'Value': 38.56171,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR216.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334417Z',\n", + " 'Value': 39.039494,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR215.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324421Z',\n", + " 'Value': 38.67933,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR217.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334419Z',\n", + " 'Value': 39.831093,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR218.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7224459Z',\n", + " 'Value': 38.3613,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR220.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7044497Z',\n", + " 'Value': 39.083828,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR219.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920155Z',\n", + " 'Value': 38.58721,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR202.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344008Z',\n", + " 'Value': 39.726665,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR201.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254466Z',\n", + " 'Value': 39.913795,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR203.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324456Z',\n", + " 'Value': 38.191315,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR204.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334282Z',\n", + " 'Value': 39.184402,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR206.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334403Z',\n", + " 'Value': 38.127216,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR205.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334321Z',\n", + " 'Value': 39.79701,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR207.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7266076Z',\n", + " 'Value': 40.08256,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR208.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344027Z',\n", + " 'Value': 38.74573,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR210.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334414Z',\n", + " 'Value': 39.827778,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR209.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344029Z',\n", + " 'Value': 39.591526,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR162.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334548Z',\n", + " 'Value': 39.351402,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR161.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334546Z',\n", + " 'Value': 39.36829,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR163.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314201Z',\n", + " 'Value': 38.836494,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR164.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334184Z',\n", + " 'Value': 1.5909697,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR166.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7184313Z',\n", + " 'Value': 39.76395,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR165.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733455Z',\n", + " 'Value': 39.817993,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR167.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7094313Z',\n", + " 'Value': 39.418304,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR168.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134379Z',\n", + " 'Value': 39.135853,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR170.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6964047Z',\n", + " 'Value': 38.71799,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR169.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334249Z',\n", + " 'Value': 39.09251,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR172.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334514Z',\n", + " 'Value': 38.719414,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR171.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734401Z',\n", + " 'Value': 39.547386,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR173.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930072Z',\n", + " 'Value': 38.697216,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR174.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930058Z',\n", + " 'Value': 39.425728,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR176.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334524Z',\n", + " 'Value': 39.991196,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR175.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334528Z',\n", + " 'Value': 39.17914,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR177.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344014Z',\n", + " 'Value': 38.37678,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR178.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930256Z',\n", + " 'Value': 39.94758,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR180.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7114229Z',\n", + " 'Value': 39.66571,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR179.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334114Z',\n", + " 'Value': 39.904457,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR182.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7164385Z',\n", + " 'Value': 39.915752,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR181.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733432Z',\n", + " 'Value': 39.03744,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR183.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7094193Z',\n", + " 'Value': 39.73549,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR184.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7144389Z',\n", + " 'Value': 38.246742,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR186.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733435Z',\n", + " 'Value': 39.452206,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR185.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344018Z',\n", + " 'Value': 38.31337,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR187.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054002Z',\n", + " 'Value': 38.719414,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR188.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334194Z',\n", + " 'Value': 40.19374,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR190.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254267Z',\n", + " 'Value': 40.089172,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR189.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334301Z',\n", + " 'Value': 38.27148,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR192.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994457Z',\n", + " 'Value': 38.218952,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR191.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344021Z',\n", + " 'Value': 38.234486,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR193.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733424Z',\n", + " 'Value': 39.944366,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR194.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.733436Z',\n", + " 'Value': 39.093487,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR196.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.73345Z',\n", + " 'Value': 38.188835,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR195.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314405Z',\n", + " 'Value': 40.10935,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR197.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.715433Z',\n", + " 'Value': 38.770123,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR198.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.704425Z',\n", + " 'Value': 38.546158,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR200.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7174238Z',\n", + " 'Value': 39.48254,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR199.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7083995Z',\n", + " 'Value': 39.31738,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR151.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334537Z',\n", + " 'Value': 38.207817,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR152.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334538Z',\n", + " 'Value': 39.943874,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR153.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334203Z',\n", + " 'Value': 39.43975,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR154.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7324353Z',\n", + " 'Value': 39.81792,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR155.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334539Z',\n", + " 'Value': 39.971394,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR156.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334117Z',\n", + " 'Value': 38.746563,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR157.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334385Z',\n", + " 'Value': 38.872536,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR158.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334286Z',\n", + " 'Value': 39.908062,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR159.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334404Z',\n", + " 'Value': 39.463634,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR160.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334284Z',\n", + " 'Value': 38.842228,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR141.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004184Z',\n", + " 'Value': 40.222397,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR142.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054172Z',\n", + " 'Value': 40.08209,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR143.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334534Z',\n", + " 'Value': 38.200974,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR144.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7194385Z',\n", + " 'Value': -45.325176,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR145.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.692042Z',\n", + " 'Value': 38.298748,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR146.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910275Z',\n", + " 'Value': 39.455357,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR147.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6939999Z',\n", + " 'Value': 39.66239,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR148.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7044486Z',\n", + " 'Value': 39.985497,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR149.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334352Z',\n", + " 'Value': 39.775578,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR150.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.725401Z',\n", + " 'Value': 39.349552,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994258Z',\n", + " 'Value': 38.619766,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR002.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344123Z',\n", + " 'Value': 39.452244,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR004.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7064394Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR003.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930134Z',\n", + " 'Value': 38.533504,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR005.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004372Z',\n", + " 'Value': 39.131474,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR006.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344135Z',\n", + " 'Value': 39.29539,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR008.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734414Z',\n", + " 'Value': 39.82251,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR007.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344138Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR009.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.713419Z',\n", + " 'Value': 39.620743,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR010.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7334227Z',\n", + " 'Value': 39.736233,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR011.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940095Z',\n", + " 'Value': 39.127758,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR012.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154359Z',\n", + " 'Value': 38.72058,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR014.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7084069Z',\n", + " 'Value': 38.4853,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR013.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344152Z',\n", + " 'Value': 38.62004,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR015.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154365Z',\n", + " 'Value': 38.32721,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR016.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974335Z',\n", + " 'Value': 38.61559,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR018.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344161Z',\n", + " 'Value': 38.171993,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR017.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920385Z',\n", + " 'Value': 38.44734,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR019.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7266203Z',\n", + " 'Value': 39.895794,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR020.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344165Z',\n", + " 'Value': 38.194336,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR021.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344167Z',\n", + " 'Value': 39.926956,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR022.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344067Z',\n", + " 'Value': 38.314487,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR024.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7194497Z',\n", + " 'Value': 39.926956,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR023.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7144503Z',\n", + " 'Value': 40.20513,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR025.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124173Z',\n", + " 'Value': 39.44503,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR026.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344173Z',\n", + " 'Value': 40.160927,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR028.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344176Z',\n", + " 'Value': 40.20513,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR027.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344175Z',\n", + " 'Value': 39.586636,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR029.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994124Z',\n", + " 'Value': 39.20667,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR030.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254492Z',\n", + " 'Value': 38.824375,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR031.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7084363Z',\n", + " 'Value': 40.023903,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR032.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344179Z',\n", + " 'Value': 39.44503,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR034.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004476Z',\n", + " 'Value': 38.573963,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR033.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014445Z',\n", + " 'Value': 38.834164,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR035.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344039Z',\n", + " 'Value': 39.343937,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR036.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940345Z',\n", + " 'Value': 39.586636,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR038.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134387Z',\n", + " 'Value': 38.200493,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR037.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7074343Z',\n", + " 'Value': 39.889774,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR039.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7234116Z',\n", + " 'Value': 39.403175,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR040.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.695015Z',\n", + " 'Value': 39.20667,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR071.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344262Z',\n", + " 'Value': 38.466988,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR072.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344073Z',\n", + " 'Value': 38.3039,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR074.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344269Z',\n", + " 'Value': 39.958927,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR073.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344266Z',\n", + " 'Value': 38.537994,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR075.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7074089Z',\n", + " 'Value': 39.86813,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR076.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344272Z',\n", + " 'Value': 38.460796,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR078.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344282Z',\n", + " 'Value': 39.002003,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR077.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7034061Z',\n", + " 'Value': -36.51047,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR079.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.705411Z',\n", + " 'Value': 38.37783,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR080.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6950392Z',\n", + " 'Value': 39.860077,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR061.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004229Z',\n", + " 'Value': 38.188156,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR062.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930322Z',\n", + " 'Value': 39.5255,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR064.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920054Z',\n", + " 'Value': 39.638214,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR063.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6961584Z',\n", + " 'Value': 38.321075,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR065.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273664Z',\n", + " 'Value': 38.181065,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR066.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344248Z',\n", + " 'Value': 39.537987,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR068.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910283Z',\n", + " 'Value': 38.888752,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR067.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7266143Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR069.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6973978Z',\n", + " 'Value': 38.537754,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR070.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.727366Z',\n", + " 'Value': 39.255383,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR051.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7084125Z',\n", + " 'Value': 40.22105,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR052.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7264027Z',\n", + " 'Value': 38.229202,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR054.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344222Z',\n", + " 'Value': 38.11777,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR053.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963838Z',\n", + " 'Value': 39.153023,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR055.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974349Z',\n", + " 'Value': 40.17522,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR056.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7304408Z',\n", + " 'Value': 38.120895,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR058.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344077Z',\n", + " 'Value': 38.930454,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR057.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124171Z',\n", + " 'Value': 40.121517,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR059.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7064135Z',\n", + " 'Value': 39.926296,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR060.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344234Z',\n", + " 'Value': 40.13747,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR041.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7184091Z',\n", + " 'Value': 39.453804,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR042.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344183Z',\n", + " 'Value': 39.231907,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR044.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344203Z',\n", + " 'Value': 39.209846,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR043.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7054259Z',\n", + " 'Value': 38.497158,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR045.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7304277Z',\n", + " 'Value': 39.8807,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR046.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344206Z',\n", + " 'Value': 39.002983,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR048.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974365Z',\n", + " 'Value': 38.303642,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR047.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014289Z',\n", + " 'Value': 38.34986,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR049.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6960056Z',\n", + " 'Value': 40.061825,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR050.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344212Z',\n", + " 'Value': 38.92439,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR081.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344286Z',\n", + " 'Value': 38.967426,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR082.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124088Z',\n", + " 'Value': 39.281673,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR084.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7254062Z',\n", + " 'Value': 38.76199,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR083.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.708429Z',\n", + " 'Value': 39.0392,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR085.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7234315Z',\n", + " 'Value': 39.83571,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR086.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344051Z',\n", + " 'Value': 38.62964,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR088.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7284067Z',\n", + " 'Value': 38.183025,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR087.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.696364Z',\n", + " 'Value': 38.6939,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR089.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344294Z',\n", + " 'Value': 39.701763,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR090.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974364Z',\n", + " 'Value': 38.381435,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR091.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344295Z',\n", + " 'Value': 39.616196,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR092.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344162Z',\n", + " 'Value': 38.873108,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR094.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344297Z',\n", + " 'Value': 39.91545,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR093.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154043Z',\n", + " 'Value': 38.477512,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR095.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344285Z',\n", + " 'Value': 40.136436,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR096.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940339Z',\n", + " 'Value': 38.663403,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR098.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294086Z',\n", + " 'Value': 38.439774,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR097.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.693015Z',\n", + " 'Value': 39.877457,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR099.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7272734Z',\n", + " 'Value': 39.851204,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR100.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344102Z',\n", + " 'Value': 40.158344,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR101.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124251Z',\n", + " 'Value': 38.515728,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR102.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344244Z',\n", + " 'Value': 38.196472,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR104.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344091Z',\n", + " 'Value': 38.189045,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR103.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344243Z',\n", + " 'Value': 38.632553,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR105.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7214259Z',\n", + " 'Value': 39.659435,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR106.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344238Z',\n", + " 'Value': 40.024128,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR108.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7104255Z',\n", + " 'Value': 39.6268,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR107.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344233Z',\n", + " 'Value': 39.79332,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR109.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7304531Z',\n", + " 'Value': 39.44428,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR110.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6910044Z',\n", + " 'Value': 38.394817,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR111.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344221Z',\n", + " 'Value': 39.986492,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR112.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734423Z',\n", + " 'Value': 40.03399,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR114.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7124071Z',\n", + " 'Value': 39.755226,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR113.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344146Z',\n", + " 'Value': 40.04204,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR115.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6920394Z',\n", + " 'Value': 39.835716,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR116.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344217Z',\n", + " 'Value': 39.754185,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR118.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134142Z',\n", + " 'Value': 39.999294,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR117.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344084Z',\n", + " 'Value': 38.19237,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR119.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930219Z',\n", + " 'Value': 39.587517,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR120.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7164391Z',\n", + " 'Value': 39.917152,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR131.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344267Z',\n", + " 'Value': 38.280064,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR132.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963713Z',\n", + " 'Value': 38.88264,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR133.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344271Z',\n", + " 'Value': 39.1591,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR134.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294527Z',\n", + " 'Value': 38.37737,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR135.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7014462Z',\n", + " 'Value': 38.770775,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR136.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7203995Z',\n", + " 'Value': 38.494747,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR137.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344072Z',\n", + " 'Value': 38.563236,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR138.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344106Z',\n", + " 'Value': 38.59732,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR139.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7224Z',\n", + " 'Value': 39.523342,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR140.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7034422Z',\n", + " 'Value': 38.517887,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR121.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.72945Z',\n", + " 'Value': 39.261375,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR122.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963812Z',\n", + " 'Value': 38.122307,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR123.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963886Z',\n", + " 'Value': 38.365185,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR124.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344137Z',\n", + " 'Value': 38.395332,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR125.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344134Z',\n", + " 'Value': 38.455627,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR126.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344131Z',\n", + " 'Value': 38.452053,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR127.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344129Z',\n", + " 'Value': 38.512604,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR128.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314364Z',\n", + " 'Value': 38.163208,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR129.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734428Z',\n", + " 'Value': 39.90843,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR130.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344275Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR242.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004019Z',\n", + " 'Value': 38.816906,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR241.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344195Z',\n", + " 'Value': 38.80518,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR243.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344188Z',\n", + " 'Value': 40.1562,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR244.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244217Z',\n", + " 'Value': 38.288567,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR246.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344201Z',\n", + " 'Value': 39.96346,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR245.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930369Z',\n", + " 'Value': 38.370255,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR247.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7074223Z',\n", + " 'Value': 38.583477,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR248.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7154373Z',\n", + " 'Value': 38.079945,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR250.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344083Z',\n", + " 'Value': 38.294643,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR249.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344334Z',\n", + " 'Value': 40.164425,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR252.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7304554Z',\n", + " 'Value': 39.0754,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR251.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344216Z',\n", + " 'Value': 40.179806,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR253.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6984011Z',\n", + " 'Value': 38.404854,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR254.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344144Z',\n", + " 'Value': 38.72932,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR256.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344219Z',\n", + " 'Value': 39.478455,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR255.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344228Z',\n", + " 'Value': 38.613785,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR257.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344178Z',\n", + " 'Value': 39.513885,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR258.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344367Z',\n", + " 'Value': 40.09816,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR260.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344317Z',\n", + " 'Value': 38.45213,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR259.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7134265Z',\n", + " 'Value': 40.090103,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR262.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344113Z',\n", + " 'Value': 25.000477,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR261.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244233Z',\n", + " 'Value': 24.251741,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR263.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344321Z',\n", + " 'Value': 23.701147,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR264.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940478Z',\n", + " 'Value': 24.541632,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR266.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344327Z',\n", + " 'Value': 23.477318,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR265.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344323Z',\n", + " 'Value': 24.753544,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR267.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344325Z',\n", + " 'Value': 24.724838,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR268.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344218Z',\n", + " 'Value': 24.80207,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR270.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344339Z',\n", + " 'Value': 25.085176,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR269.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7314276Z',\n", + " 'Value': 24.138388,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR272.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244115Z',\n", + " 'Value': 24.278738,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR271.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344336Z',\n", + " 'Value': 23.923967,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR273.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344335Z',\n", + " 'Value': 23.215237,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR274.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344345Z',\n", + " 'Value': 24.152054,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR276.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7004027Z',\n", + " 'Value': 24.990276,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR275.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940062Z',\n", + " 'Value': 24.489922,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR277.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7094139Z',\n", + " 'Value': 24.46337,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR278.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344368Z',\n", + " 'Value': 24.551352,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR280.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344287Z',\n", + " 'Value': 25.195246,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR279.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.709427Z',\n", + " 'Value': 23.609083,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR232.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7284497Z',\n", + " 'Value': 39.403683,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR231.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344236Z',\n", + " 'Value': 39.96489,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR233.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344274Z',\n", + " 'Value': 39.986343,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR234.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344071Z',\n", + " 'Value': 39.628845,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR236.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344259Z',\n", + " 'Value': 39.269802,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR235.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344265Z',\n", + " 'Value': 38.74258,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR237.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344261Z',\n", + " 'Value': 40.024624,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR238.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344365Z',\n", + " 'Value': 40.317173,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR240.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344181Z',\n", + " 'Value': 39.013767,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR239.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7274155Z',\n", + " 'Value': 39.812885,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR222.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.701408Z',\n", + " 'Value': 39.133728,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR221.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6984281Z',\n", + " 'Value': 38.27586,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR223.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7184391Z',\n", + " 'Value': 40.24217,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR224.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963908Z',\n", + " 'Value': 39.39085,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR226.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344232Z',\n", + " 'Value': 40.151733,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR225.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734409Z',\n", + " 'Value': 39.305435,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR227.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344237Z',\n", + " 'Value': 39.597782,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR228.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7094363Z',\n", + " 'Value': 40.057266,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR230.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734427Z',\n", + " 'Value': 39.52029,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR229.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7024202Z',\n", + " 'Value': 39.219227,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR212.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344115Z',\n", + " 'Value': 38.48031,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR211.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930374Z',\n", + " 'Value': 39.97267,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR213.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734415Z',\n", + " 'Value': 38.208794,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR214.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930002Z',\n", + " 'Value': 39.026394,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR216.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6940152Z',\n", + " 'Value': 39.156128,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR215.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7114368Z',\n", + " 'Value': 38.513325,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR217.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6974046Z',\n", + " 'Value': 39.1014,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR218.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994366Z',\n", + " 'Value': 38.13853,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR220.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344247Z',\n", + " 'Value': 38.9195,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR219.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344362Z',\n", + " 'Value': 40.11748,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR202.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6994289Z',\n", + " 'Value': 38.300476,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR201.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6963786Z',\n", + " 'Value': 40.036777,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR203.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7294349Z',\n", + " 'Value': 40.1832,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR204.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344132Z',\n", + " 'Value': 39.740383,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR206.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7244072Z',\n", + " 'Value': 39.898827,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR205.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7044248Z',\n", + " 'Value': 39.098427,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR207.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.734413Z',\n", + " 'Value': 38.863567,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR208.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7344279Z',\n", + " 'Value': 39.586384,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR210.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.6930201Z',\n", + " 'Value': 40.238556,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR209.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7104104Z',\n", + " 'Value': 38.3749,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", + " 'Namespace': 3,\n", + " 'IdType': 1,\n", + " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", + " 'Value': 0,\n", + " 'ValueType': 'Float',\n", + " 'StatusCode': None,\n", + " 'StatusSymbol': None},\n", + " ...]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Live value data of trackers\n", "live_value = opc_data.get_values(\n", @@ -268,9 +9177,215 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TimestampValueTypeValueStatusCodeStatusSymbolIdTypeIdNamespace
02024-08-27T13:49:34.283241ZDouble2420.1223141Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
12024-08-27T13:50:34.283241ZDouble2926.2788091Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
22024-08-27T13:51:34.283241ZDouble2981.6279301Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
32024-08-27T13:52:34.283241ZDouble3375.9743651Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
42024-08-27T13:53:34.283241ZDouble3476.2563481Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
...........................
42307152024-08-28T13:44:34.283241ZDouble6677.9018551Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307162024-08-28T13:45:34.283241ZDouble6677.9018551Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307172024-08-28T13:46:34.283241ZDouble5503.1015621Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307182024-08-28T13:47:34.283241ZDouble3167.9548341Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307192024-08-28T13:48:34.283241ZDouble5536.0825201Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
\n", + "

4230720 rows × 8 columns

\n", + "
" + ], + "text/plain": [ + " Timestamp ValueType Value StatusCode \\\n", + "0 2024-08-27T13:49:34.283241Z Double 2420.122314 1 \n", + "1 2024-08-27T13:50:34.283241Z Double 2926.278809 1 \n", + "2 2024-08-27T13:51:34.283241Z Double 2981.627930 1 \n", + "3 2024-08-27T13:52:34.283241Z Double 3375.974365 1 \n", + "4 2024-08-27T13:53:34.283241Z Double 3476.256348 1 \n", + "... ... ... ... ... \n", + "4230715 2024-08-28T13:44:34.283241Z Double 6677.901855 1 \n", + "4230716 2024-08-28T13:45:34.283241Z Double 6677.901855 1 \n", + "4230717 2024-08-28T13:46:34.283241Z Double 5503.101562 1 \n", + "4230718 2024-08-28T13:47:34.283241Z Double 3167.954834 1 \n", + "4230719 2024-08-28T13:48:34.283241Z Double 5536.082520 1 \n", + "\n", + " StatusSymbol IdType \\\n", + "0 Good 1 \n", + "1 Good 1 \n", + "2 Good 1 \n", + "3 Good 1 \n", + "4 Good 1 \n", + "... ... ... \n", + "4230715 Good 1 \n", + "4230716 Good 1 \n", + "4230717 Good 1 \n", + "4230718 Good 1 \n", + "4230719 Good 1 \n", + "\n", + " Id Namespace \n", + "0 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "1 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "2 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "3 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "4 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "... ... ... \n", + "4230715 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4230716 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4230717 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4230718 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4230719 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "\n", + "[4230720 rows x 8 columns]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# 1 day aggregated historical data\n", "one_day_historical_data = opc_data.get_historical_aggregated_values(\n", @@ -285,9 +9400,215 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TimestampValueTypeValueStatusCode.CodeStatusCode.SymbolIdTypeIdNamespace
02024-07-13T00:00:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
12024-07-13T00:01:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
22024-07-13T00:02:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
32024-07-13T00:03:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
42024-07-13T00:04:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
...........................
42277772024-07-13T23:54:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277782024-07-13T23:55:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277792024-07-13T23:56:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277802024-07-13T23:57:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277812024-07-13T23:58:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
\n", + "

4227782 rows × 8 columns

\n", + "
" + ], + "text/plain": [ + " Timestamp ValueType Value StatusCode.Code \\\n", + "0 2024-07-13T00:00:00Z Double 0.0 1.083507e+09 \n", + "1 2024-07-13T00:01:00Z Double 0.0 1.083507e+09 \n", + "2 2024-07-13T00:02:00Z Double 0.0 1.083507e+09 \n", + "3 2024-07-13T00:03:00Z Double 0.0 1.083507e+09 \n", + "4 2024-07-13T00:04:00Z Double 0.0 1.083507e+09 \n", + "... ... ... ... ... \n", + "4227777 2024-07-13T23:54:00Z Double 0.0 1.083507e+09 \n", + "4227778 2024-07-13T23:55:00Z Double 0.0 1.083507e+09 \n", + "4227779 2024-07-13T23:56:00Z Double 0.0 1.083507e+09 \n", + "4227780 2024-07-13T23:57:00Z Double 0.0 1.083507e+09 \n", + "4227781 2024-07-13T23:58:00Z Double 0.0 1.083507e+09 \n", + "\n", + " StatusCode.Symbol IdType \\\n", + "0 UncertainSubNormal 1 \n", + "1 UncertainSubNormal 1 \n", + "2 UncertainSubNormal 1 \n", + "3 UncertainSubNormal 1 \n", + "4 UncertainSubNormal 1 \n", + "... ... ... \n", + "4227777 UncertainSubNormal 1 \n", + "4227778 UncertainSubNormal 1 \n", + "4227779 UncertainSubNormal 1 \n", + "4227780 UncertainSubNormal 1 \n", + "4227781 UncertainSubNormal 1 \n", + "\n", + " Id Namespace \n", + "0 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "1 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "2 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "3 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "4 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", + "... ... ... \n", + "4227777 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4227778 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4227779 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4227780 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "4227781 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", + "\n", + "[4227782 rows x 8 columns]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# 1 day raw historical data\n", "one_day_raw_historical_data = opc_data.get_raw_historical_values(\n", diff --git a/notebooks/api_performance_testing.ipynb b/notebooks/api_performance_testing.ipynb deleted file mode 100644 index 5022eda..0000000 --- a/notebooks/api_performance_testing.ipynb +++ /dev/null @@ -1,2188 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook explores both model index and opc ua scripts and contain examples of all the functions to make request to model index api and opc ua api servers. " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Import Libraries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import the required packeages\n", - "import pandas as pd\n", - "import os\n", - "import json\n", - "import datetime\n", - "import concurrent.futures\n", - "from dotenv import load_dotenv\n", - "from pathlib import Path\n", - "from dateutil.relativedelta import relativedelta" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Import Scripts" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Import model index functions\n", - "from pyprediktormapclient.model_index import ModelIndex\n", - "\n", - "# Import OPC UA functions\n", - "from pyprediktormapclient.opc_ua import OPC_UA\n", - "\n", - "# Import Analytics Helper\n", - "from pyprediktormapclient.analytics_helper import AnalyticsHelper\n", - "\n", - "# Import \"Dataframer\" Tools\n", - "from pyprediktormapclient.shared import *\n", - "\n", - "# import AUTH_CLIENT\n", - "from pyprediktormapclient.auth_client import AUTH_CLIENT" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", - "dotenv_path = Path(\".env\")\n", - "load_dotenv(dotenv_path=dotenv_path)\n", - "\n", - "username = os.environ[\"USERNAME\"]\n", - "password = os.environ[\"PASSWORD\"]\n", - "opcua_rest_url = os.environ[\"OPC_UA_REST_URL\"]\n", - "opcua_server_url = os.environ[\"OPC_UA_SERVER_URL\"]\n", - "model_index_url = os.environ[\"MODEL_INDEX_URL\"]\n", - "ory_url = os.environ[\"ORY_URL\"]\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Getting ory bearer token\n", - "auth_client = AUTH_CLIENT(rest_url=ory_url, username=username, password=password)\n", - "auth_client.request_new_ory_token()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Connecting to ModelIndex APIs \n", - "model = ModelIndex(url=model_index_url, auth_client=auth_client, session=auth_client.session)\n", - "\n", - "# Listed sites on the model index api server\n", - "namespaces = model.get_namespace_array()\n", - "# Types of Objects\n", - "object_types_json = model.get_object_types()\n", - "object_types = AnalyticsHelper(object_types_json)\n", - "namespace_list = object_types.namespaces_as_list(namespaces)\n", - "\n", - "# Initate the OPC UA API with a fixed namespace list\n", - "opc_data = OPC_UA(rest_url=opcua_rest_url, opcua_url=opcua_server_url, namespaces=namespace_list, auth_client=auth_client)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Download data from modelindex api" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Unique types of Objects\n", - "object_types_unique = object_types.dataframe[[\"Id\", \"Name\"]].drop_duplicates()\n", - "object_types_unique" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# To get the objects of a type\n", - "sites_json = model.get_objects_of_type(\"SiteType\")\n", - "\n", - "# Send the returned JSON into a normalizer to get Id, Type, Name, Props and Vars as columns\n", - "sites = AnalyticsHelper(sites_json)\n", - "sites.list_of_names()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Analytics helper\n", - "sites.variables_as_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sites.list_of_ids()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Selecting the second site\n", - "first_site_id = sites.list_of_ids()[0]\n", - "# first_site_id = '14:1:BE.DK-ADU'\n", - "first_site_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get all stringsets for one park\n", - "string_sets_for_first_park_as_json = model.get_object_descendants(\n", - " \"StringSetType\", [first_site_id], \"PV_Assets\"\n", - ")\n", - "string_sets = AnalyticsHelper(string_sets_for_first_park_as_json)\n", - "string_sets.dataframe" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Query Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "variable_list =string_sets.variables_as_list([\"DCPower\"])\n", - "start_time = datetime.datetime(2023, 11, 13, 00, 00)\n", - "end_time = datetime.datetime(2023, 11, 13, 23, 59)\n", - "pro_interval=60*1000\n", - "agg_name=\"Average\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Batching with Async Refactoring" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import asyncio\n", - "import aiohttp\n", - "from aiohttp import ClientSession\n", - "from asyncio import Semaphore\n", - "from datetime import timedelta\n", - "from typing import Dict, List, Tuple\n", - "\n", - "logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", - "logger = logging.getLogger(__name__)\n", - "\n", - "async def generate_time_batches(start_time: datetime, end_time: datetime, pro_interval: int, max_data_points: int) -> List[tuple]:\n", - " \"\"\"Generate time batches based on start time, end time, processing interval, and batch size\"\"\"\n", - "\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / pro_interval\n", - " \n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - "\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " return total_time_range_ms, max_variables_per_batch, time_batch_size_ms, max_time_batches\n", - "\n", - "def generate_variable_batches(start_time, end_time, pro_interval, variable_list: List[Dict[str, str]], max_data_points) -> List:\n", - " \"\"\"Generate variable batches based on the variable list and batch size\"\"\"\n", - "\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in variable_list]\n", - " max_variables_per_batch = generate_time_batches(start_time, end_time, pro_interval, max_data_points)[1]\n", - "\n", - " variable_batches = [\n", - " extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)\n", - " ]\n", - "\n", - " return variable_batches\n", - "\n", - "def _prepare_body(\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " variable_list: List[Dict[str, str]], \n", - " agg_name: str,\n", - " ) -> Dict:\n", - " \"\"\"\n", - " Prepare the request body for the API call.\n", - " \"\"\"\n", - " total_time_range_ms, max_variables_per_batch, time_batch_size_ms, max_time_batches = generate_time_batches(\n", - " start_time, end_time, pro_interval, 10000)\n", - "\n", - " for time_batch in range(max_time_batches):\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " variable_batches = generate_variable_batches(variable_list)\n", - "\n", - " for variables in variable_batches:\n", - " body = {\n", - " **opc_data.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"ReadValueIds\": variables,\n", - " \"AggregateName\": agg_name\n", - " }\n", - " return body\n", - " \n", - "def process_batch(content: dict) -> pd.DataFrame:\n", - " \"\"\" Process individual batch of data \"\"\"\n", - " \n", - " df_list = []\n", - " for item in content[\"HistoryReadResults\"]:\n", - " df = pd.json_normalize(item[\"DataValues\"])\n", - " for key, value in item[\"NodeId\"].items():\n", - " df[f\"HistoryReadResults.NodeId.{key}\"] = value\n", - " df_list.append(df)\n", - " \n", - " if df_list:\n", - " df_result = pd.concat(df_list)\n", - " df_result.reset_index(inplace=True, drop=True)\n", - " return df_result\n", - " else:\n", - " return pd.DataFrame()\n", - " \n", - "async def make_async_api_request(opc_data, start_time:datetime, end_time:datetime,\n", - " pro_interval: int, variable_list: List[Dict[str, str]], agg_name: str,\n", - " semaphore, max_retries: int = 3, retry_delay: int = 5) -> dict:\n", - " \n", - " \"\"\"Make API request for the given time range and variable list\"\"\"\n", - "\n", - " async with semaphore:\n", - " body = _prepare_body(\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " variable_list,\n", - " agg_name\n", - " )\n", - " for attempt in range(max_retries):\n", - " try:\n", - " async with ClientSession() as session:\n", - " async with session.post(\n", - " f\"{opcua_rest_url}values/historicalaggregated\",\n", - " json=body,\n", - " headers=opc_data.headers\n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()\n", - " break\n", - " except aiohttp.ClientError as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " opc_data._check_content(content)\n", - "\n", - " df_result = process_batch(content)\n", - " return df_result\n", - " \n", - "async def process_api_response(opc_data, start_time:datetime, end_time:datetime,\n", - " pro_interval: int, variable_list: List[Dict[str, str]], agg_name: str,\n", - " max_concurrent_requests: int = 10) -> pd.DataFrame:\n", - " \"\"\" Process API response asynchronously and return the result dataframe \"\"\"\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - "\n", - " tasks = [\n", - " make_async_api_request(opc_data, start_time, end_time, pro_interval, variable_list, agg_name, semaphore)\n", - " ]\n", - " results = await asyncio.gather(*tasks)\n", - " all_results.extend(results)\n", - " \n", - " if all_results:\n", - " combined_df = pd.concat(all_results, ignore_index=True)\n", - " combined_df.reset_index(inplace=True, drop=True)\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " return opc_data._process_df(combined_df, columns)\n", - " else:\n", - " return pd.DataFrame()\n", - " \n", - "async def get_historical_aggregated_values_async(\n", - " opc_data,\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " variable_list: List[Dict[str, str]],\n", - " agg_name: str,\n", - ") -> pd.DataFrame:\n", - " \"\"\"Request historical aggregated values from the OPC UA server with batching\"\"\"\n", - "\n", - " \n", - " result_df = await process_api_response(opc_data, start_time, end_time, pro_interval, variable_list, agg_name)\n", - "\n", - " return result_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical inverter data in asyncio process\n", - "one_days_historic_inverter_data2 = await get_historical_aggregated_values_batch_time_vars_async(\n", - " start_time=start_time,\n", - " end_time=end_time,\n", - " pro_interval=60*1000,\n", - " agg_name=\"Average\",\n", - " variable_list=string_sets.variables_as_list([\"DCPower\"])\n", - ")\n", - "one_days_historic_inverter_data2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Batching with Async" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import asyncio\n", - "import aiohttp\n", - "from aiohttp import ClientSession\n", - "from asyncio import Semaphore\n", - "from datetime import timedelta\n", - "\n", - "logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", - "logger = logging.getLogger(__name__)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_historical_aggregated_values_batch_time_vars_async(\n", - " self, \n", - " start_time: datetime, \n", - " end_time: datetime, \n", - " pro_interval: int, \n", - " agg_name: str, \n", - " variable_list: list, \n", - " max_data_points: int = 10000, \n", - " max_retries: int = 3, \n", - " retry_delay: int = 5, \n", - " max_concurrent_requests: int = 10\n", - ") -> pd.DataFrame:\n", - " \n", - " \"\"\"Request historical aggregated values from the OPC UA server with batching\"\"\"\n", - "\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / pro_interval\n", - "\n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in variable_list]\n", - " variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)]\n", - "\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - "\n", - " async def process_batch(variables, time_batch):\n", - " async with semaphore:\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " body = {\n", - " **self.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"ReadValueIds\": variables,\n", - " \"AggregateName\": agg_name\n", - " }\n", - "\n", - " for attempt in range(max_retries):\n", - " try:\n", - " async with ClientSession() as session:\n", - " async with session.post(\n", - " f\"{self.rest_url}values/historicalaggregated\",\n", - " json=body,\n", - " headers=self.headers\n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()\n", - " break\n", - " except aiohttp.ClientError as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " self._check_content(content)\n", - "\n", - " df_list = []\n", - " for item in content[\"HistoryReadResults\"]:\n", - " df = pd.json_normalize(item[\"DataValues\"])\n", - " for key, value in item[\"NodeId\"].items():\n", - " df[f\"HistoryReadResults.NodeId.{key}\"] = value\n", - " df_list.append(df)\n", - " \n", - " if df_list:\n", - " df_result = pd.concat(df_list)\n", - " df_result.reset_index(inplace=True, drop=True)\n", - " return df_result\n", - "\n", - " tasks = [\n", - " process_batch(variables, time_batch)\n", - " for variables in variable_batches\n", - " for time_batch in range(max_time_batches)\n", - " ]\n", - "\n", - " results = await asyncio.gather(*tasks)\n", - " all_results.extend(results)\n", - "\n", - " logger.info(\"Combining all batches...\")\n", - " combined_df = pd.concat(all_results, ignore_index=True)\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " return self._process_df(combined_df, columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical data\n", - "one_day_historical_data = await get_historical_aggregated_values_batch_time_vars_async(\n", - " opc_data,\n", - " start_time=start_time,\n", - " end_time=end_time,\n", - " pro_interval=pro_interval,\n", - " agg_name=agg_name,\n", - " variable_list=variable_list,\n", - " max_data_points=10000,\n", - " max_concurrent_requests=35\n", - ")\n", - "one_day_historical_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Batching with Async for Raw Historical Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Dict, List, Any, Union, Optional" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_raw_historical_values_batch_time_vars_async(\n", - " self, \n", - " start_time: datetime, \n", - " end_time: datetime, \n", - " variable_list: list, \n", - " limit_start_index: Union[int, None] = None, \n", - " limit_num_records: Union[int, None] = None,\n", - " max_data_points: int = 10000, \n", - " max_retries: int = 3, \n", - " retry_delay: int = 5, \n", - " max_concurrent_requests: int = 10\n", - ") -> pd.DataFrame:\n", - " \n", - " \"\"\"Request historical aggregated values from the OPC UA server with batching\"\"\"\n", - "\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / max_data_points\n", - "\n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " extended_variables = [{\"NodeId\": var} for var in variable_list]\n", - " variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)]\n", - "\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - "\n", - " async def process_batch(variables, time_batch):\n", - " async with semaphore:\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " body = {\n", - " **self.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ReadValueIds\": variables,\n", - " }\n", - " \n", - " if limit_start_index is not None and limit_num_records is not None:\n", - " body[\"Limit\"] = {\"StartIndex\": limit_start_index, \"NumRecords\": limit_num_records}\n", - "\n", - " for attempt in range(max_retries):\n", - " try:\n", - " async with ClientSession() as session:\n", - " async with session.post(\n", - " f\"{self.rest_url}values/historical\",\n", - " json=body,\n", - " headers=self.headers\n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()\n", - " break\n", - " except aiohttp.ClientError as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " self._check_content(content)\n", - "\n", - " df_list = []\n", - " for item in content[\"HistoryReadResults\"]:\n", - " df = pd.json_normalize(item[\"DataValues\"])\n", - " for key, value in item[\"NodeId\"].items():\n", - " df[f\"HistoryReadResults.NodeId.{key}\"] = value\n", - " df_list.append(df)\n", - " \n", - " if df_list:\n", - " df_result = pd.concat(df_list)\n", - " df_result.reset_index(inplace=True, drop=True)\n", - " return df_result\n", - "\n", - " tasks = [\n", - " process_batch(variables, time_batch)\n", - " for variables in variable_batches\n", - " for time_batch in range(max_time_batches)\n", - " ]\n", - "\n", - " results = await asyncio.gather(*tasks)\n", - " all_results.extend(results)\n", - "\n", - " logger.info(\"Combining all batches...\")\n", - " combined_df = pd.concat(all_results, ignore_index=True)\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " return self._process_df(combined_df, columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day raw historical data\n", - "one_day_raw_historical_data = await get_raw_historical_values_batch_time_vars_async(\n", - " opc_data,\n", - " start_time=start_time,\n", - " end_time=end_time,\n", - " variable_list=variable_list,\n", - " max_data_points=10000,\n", - " max_concurrent_requests=35\n", - ")\n", - "one_day_raw_historical_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Async with ClientPool" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "import aiohttp\n", - "from aiohttp import ClientSession\n", - "from asyncio import Semaphore\n", - "from typing import List, Dict, Any\n", - "from datetime import datetime, timedelta\n", - "import pandas as pd\n", - "import logging\n", - "from pydantic import AnyUrl, ValidationError" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class ClientPool:\n", - " def __init__(self, num_clients: int, rest_url: str, headers: Dict[str, str]):\n", - " self.clients = asyncio.Queue()\n", - " for _ in range(num_clients):\n", - " self.clients.put_nowait(aiohttp.ClientSession(base_url=rest_url, headers=headers))\n", - " self.num_clients = num_clients\n", - "\n", - " async def get_client(self):\n", - " client = await self.clients.get()\n", - " return client\n", - "\n", - " async def release_client(self, client):\n", - " await self.clients.put(client)\n", - "\n", - " async def close_all(self):\n", - " while not self.clients.empty():\n", - " client = await self.clients.get()\n", - " await client.close()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def request_from_api_async(\n", - " client_pool: ClientPool,\n", - " method: str,\n", - " endpoint: str,\n", - " data: str = None,\n", - " params: Dict[str, Any] = None,\n", - " extended_timeout: bool = False,\n", - ") -> Dict[str, Any]:\n", - " timeout = aiohttp.ClientTimeout(total=300 if extended_timeout else 30)\n", - " client = await client_pool.get_client()\n", - " \n", - " try:\n", - " if method == \"GET\":\n", - " async with client.get(endpoint, params=params, timeout=timeout) as response:\n", - " response.raise_for_status()\n", - " if 'application/json' in response.headers.get('Content-Type', ''):\n", - " return await response.json()\n", - " else:\n", - " return {\"error\": \"Non-JSON response\", \"content\": await response.text()}\n", - " elif method == \"POST\":\n", - " async with client.post(endpoint, data=data, params=params, timeout=timeout) as response:\n", - " response.raise_for_status()\n", - " if 'application/json' in response.headers.get('Content-Type', ''):\n", - " return await response.json()\n", - " else:\n", - " return {\"error\": \"Non-JSON response\", \"content\": await response.text()}\n", - " else:\n", - " raise ValidationError(\"Unsupported method\")\n", - " finally:\n", - " await client_pool.release_client(client)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_historical_aggregated_values_batch_time_vars_async(\n", - " self,\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " agg_name: str,\n", - " variable_list: List[str],\n", - " max_data_points: int = 100000,\n", - " max_retries: int = 3,\n", - " retry_delay: int = 5,\n", - " max_concurrent_requests: int = 55\n", - ") -> pd.DataFrame:\n", - " logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", - " logger = logging.getLogger(__name__)\n", - "\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in variable_list]\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / pro_interval\n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)]\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - " client_pool = ClientPool(max_concurrent_requests, self.rest_url, self.headers)\n", - "\n", - " async def process_batch(variables, time_batch):\n", - " async with semaphore:\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " body = {\n", - " **self.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"ReadValueIds\": variables,\n", - " \"AggregateName\": agg_name\n", - " }\n", - "\n", - " for attempt in range(max_retries):\n", - " try:\n", - " content = await request_from_api_async(\n", - " client_pool,\n", - " method=\"POST\",\n", - " endpoint=f\"/values/historicalaggregated\",\n", - " data=json.dumps(body, default=self.json_serial),\n", - " extended_timeout=True\n", - " )\n", - " break\n", - " except (aiohttp.ClientError, ValidationError) as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " self._check_content(content)\n", - "\n", - " df_list = []\n", - " for item in content[\"HistoryReadResults\"]:\n", - " df = pd.json_normalize(item[\"DataValues\"])\n", - " for key, value in item[\"NodeId\"].items():\n", - " df[f\"HistoryReadResults.NodeId.{key}\"] = value\n", - " df_list.append(df)\n", - " \n", - " if df_list:\n", - " df_result = pd.concat(df_list)\n", - " df_result.reset_index(inplace=True, drop=True)\n", - " return df_result\n", - "\n", - " tasks = [\n", - " process_batch(variables, time_batch)\n", - " for variables in variable_batches\n", - " for time_batch in range(max_time_batches)\n", - " ]\n", - "\n", - " try:\n", - " results = await asyncio.gather(*tasks)\n", - " all_results.extend(results)\n", - "\n", - " logger.info(\"Combining all batches...\")\n", - " combined_df = pd.concat(all_results, ignore_index=True)\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " return self._process_df(combined_df, columns)\n", - " finally:\n", - " await client_pool.close_all()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import datetime" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical data\n", - "one_day_historical_data = await get_historical_aggregated_values_batch_time_vars_async(\n", - " opc_data,\n", - " start_time=(datetime.datetime.now() - datetime.timedelta(30)),\n", - " end_time=(datetime.datetime.now() - datetime.timedelta(29)),\n", - " pro_interval=60*1000,\n", - " agg_name=\"Average\",\n", - " variable_list=string_sets.variables_as_list([\"DCPower\"]),\n", - " max_data_points=10000,\n", - " max_concurrent_requests=100\n", - ")\n", - "one_day_historical_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Async with Data Handler" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "import aiohttp\n", - "import pandas as pd\n", - "import sqlite3\n", - "import tempfile\n", - "import os\n", - "import json\n", - "from asyncio import Semaphore\n", - "from typing import List, Dict, Any\n", - "from datetime import datetime, timedelta\n", - "import logging\n", - "import pyarrow as pa\n", - "import pyarrow.parquet as pq" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class DataHandler:\n", - " def __init__(self, max_memory_rows=10000):\n", - " self.max_memory_rows = max_memory_rows\n", - " self.temp_dir = tempfile.mkdtemp()\n", - " self.db_path = os.path.join(self.temp_dir, 'temp_data.db')\n", - " self.conn = sqlite3.connect(self.db_path)\n", - " self.conn.execute('''CREATE TABLE IF NOT EXISTS temp_data\n", - " (id INTEGER PRIMARY KEY AUTOINCREMENT,\n", - " batch_id TEXT,\n", - " data TEXT)''')\n", - "\n", - " async def save_data(self, batch_id: str, data: pd.DataFrame):\n", - " if len(data) <= self.max_memory_rows:\n", - " # Store small datasets directly in SQLite\n", - " self.conn.execute(\"INSERT INTO temp_data (batch_id, data) VALUES (?, ?)\",\n", - " (batch_id, data.to_json()))\n", - " else:\n", - " # Stream larger datasets to Parquet file\n", - " file_path = os.path.join(self.temp_dir, f\"batch_{batch_id}.parquet\")\n", - " table = pa.Table.from_pandas(data)\n", - " pq.write_table(table, file_path)\n", - " \n", - " # Store file path in SQLite\n", - " self.conn.execute(\"INSERT INTO temp_data (batch_id, data) VALUES (?, ?)\",\n", - " (batch_id, file_path))\n", - " self.conn.commit()\n", - "\n", - " async def get_data(self, batch_id: str) -> pd.DataFrame:\n", - " cursor = self.conn.execute(\"SELECT data FROM temp_data WHERE batch_id = ?\", (batch_id,))\n", - " result = cursor.fetchone()\n", - " if result:\n", - " data = result[0]\n", - " if data.startswith('{'): # JSON data\n", - " return pd.read_json(data)\n", - " else: # File path\n", - " return pd.read_parquet(data)\n", - " return None\n", - "\n", - " def cleanup(self):\n", - " self.conn.close()\n", - " for file in os.listdir(self.temp_dir):\n", - " os.remove(os.path.join(self.temp_dir, file))\n", - " os.rmdir(self.temp_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_historical_aggregated_values_batch_time_vars_data_async(\n", - " self,\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " agg_name: str,\n", - " variable_list: List[str],\n", - " max_data_points: int = 1000,\n", - " max_retries: int = 3,\n", - " retry_delay: int = 5,\n", - " max_concurrent_requests: int = 10\n", - ") -> pd.DataFrame:\n", - " logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", - " logger = logging.getLogger(__name__)\n", - "\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in variable_list]\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / pro_interval\n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)]\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - " client_pool = ClientPool(max_concurrent_requests, self.rest_url, self.headers)\n", - " data_handler = DataHandler()\n", - "\n", - " async def process_batch(vid, variables, time_batch):\n", - " async with semaphore:\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " body = {\n", - " **self.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"ReadValueIds\": variables,\n", - " \"AggregateName\": agg_name\n", - " }\n", - "\n", - " for attempt in range(max_retries):\n", - " try:\n", - " content = await request_from_api_async(\n", - " client_pool,\n", - " method=\"POST\",\n", - " endpoint=f\"/values/historicalaggregated\",\n", - " data=json.dumps(body, default=self.json_serial),\n", - " extended_timeout=True\n", - " )\n", - " break\n", - " except (aiohttp.ClientError, ValidationError) as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " self._check_content(content)\n", - "\n", - " df_result = pd.json_normalize(\n", - " content, \n", - " record_path=['HistoryReadResults', 'DataValues'], \n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], \n", - " ['HistoryReadResults', 'NodeId','Id'],\n", - " ['HistoryReadResults', 'NodeId','Namespace']]\n", - " )\n", - " batch_id = f\"{time_batch}_{vid}\"\n", - " await data_handler.save_data(batch_id, df_result)\n", - " return batch_id\n", - "\n", - " tasks = [\n", - " process_batch(vid,variables, time_batch)\n", - " for vid,variables in enumerate(variable_batches)\n", - " for time_batch in range(max_time_batches)\n", - " ]\n", - "\n", - " try:\n", - " batch_ids = await asyncio.gather(*tasks)\n", - " # for batch_id in batch_ids:\n", - " # df = await data_handler.get_data(batch_id)\n", - " # all_results.append(df)\n", - "\n", - " # logger.info(\"Combining all batches...\")\n", - " # combined_df = pd.concat(all_results, ignore_index=True)\n", - " # columns = {\n", - " # \"Value.Type\": \"ValueType\",\n", - " # \"Value.Body\": \"Value\",\n", - " # \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " # \"StatusCode.Code\": \"StatusCode\",\n", - " # \"SourceTimestamp\": \"Timestamp\",\n", - " # \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " # \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " # \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " # }\n", - " # return self._process_df(combined_df, columns)\n", - " finally:\n", - " await client_pool.close_all()\n", - " data_handler.cleanup()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical data\n", - "one_day_historical_data = await get_historical_aggregated_values_batch_time_vars_data_async(\n", - " opc_data,\n", - " start_time=start_time,\n", - " end_time=end_time,\n", - " pro_interval=pro_interval,\n", - " agg_name=agg_name,\n", - " variable_list=variable_list,\n", - " max_data_points=20000,\n", - " max_concurrent_requests=50\n", - ")\n", - "one_day_historical_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Async with parquet data handler for large data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "import aiohttp\n", - "import pandas as pd\n", - "import pyarrow as pa\n", - "import pyarrow.parquet as pq\n", - "from datetime import datetime, timedelta\n", - "import json\n", - "from typing import List, Dict, Any\n", - "import logging\n", - "from asyncio import Semaphore\n", - "from aiohttp import TCPConnector\n", - "from tenacity import retry, stop_after_attempt, wait_exponential\n", - "from concurrent.futures import ThreadPoolExecutor\n", - "\n", - "import tracemalloc\n", - "tracemalloc.start()\n", - "\n", - "logger = logging.getLogger(__name__)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class AsyncParquetWriter:\n", - " def __init__(self, filename):\n", - " self.filename = filename\n", - " self.writer = None\n", - " self.executor = ThreadPoolExecutor(max_workers=10)\n", - "\n", - " async def write(self, df):\n", - " loop = asyncio.get_running_loop()\n", - " table = pa.Table.from_pandas(df)\n", - " if self.writer is None:\n", - " self.writer = pq.ParquetWriter(self.filename, table.schema)\n", - " await loop.run_in_executor(self.executor, self.writer.write_table, table)\n", - "\n", - " async def close(self):\n", - " if self.writer:\n", - " loop = asyncio.get_running_loop()\n", - " await loop.run_in_executor(self.executor, self.writer.close)\n", - " self.writer = None\n", - "\n", - "class DataHandler:\n", - " def __init__(self, base_path):\n", - " self.base_path = base_path\n", - " self.writers = {}\n", - "\n", - " async def save_data(self, batch_id: str, data: pd.DataFrame):\n", - " if batch_id not in self.writers:\n", - " self.writers[batch_id] = AsyncParquetWriter(f\"{self.base_path}/batch_{batch_id}.parquet\")\n", - " await self.writers[batch_id].write(data)\n", - "\n", - " async def close_all(self):\n", - " for writer in self.writers.values():\n", - " await writer.close()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_historical_aggregated_values_batch_time_vars_data_async_parquet(\n", - " self,\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " agg_name: str,\n", - " variable_list: List[str],\n", - " max_data_points: int = 100000,\n", - " max_retries: int = 3,\n", - " retry_delay: int = 5,\n", - " max_concurrent_requests: int = 50\n", - ") -> pd.DataFrame:\n", - " logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", - " logger = logging.getLogger(__name__)\n", - "\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in variable_list]\n", - " total_time_range_ms = (end_time - start_time).total_seconds() * 1000\n", - " estimated_intervals = total_time_range_ms / pro_interval\n", - " max_variables_per_batch = max(1, int(max_data_points / estimated_intervals))\n", - " variable_batches = [extended_variables[i:i + max_variables_per_batch] for i in range(0, len(extended_variables), max_variables_per_batch)]\n", - " max_time_batches = max(1, int(estimated_intervals / max_data_points))\n", - " time_batch_size_ms = total_time_range_ms / max_time_batches\n", - "\n", - " all_results = []\n", - " semaphore = Semaphore(max_concurrent_requests)\n", - " client_pool = ClientPool(max_concurrent_requests, self.rest_url, self.headers)\n", - " data_handler = DataHandler(base_path=\"pqfiles\")\n", - "\n", - " async def process_batch(vid, variables, time_batch):\n", - " async with semaphore:\n", - " batch_start_ms = time_batch * time_batch_size_ms\n", - " batch_end_ms = min((time_batch + 1) * time_batch_size_ms, total_time_range_ms)\n", - " batch_start = start_time + timedelta(milliseconds=batch_start_ms)\n", - " batch_end = start_time + timedelta(milliseconds=batch_end_ms)\n", - "\n", - " body = {\n", - " **self.body,\n", - " \"StartTime\": batch_start.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"EndTime\": batch_end.strftime(\"%Y-%m-%dT%H:%M:%S.%fZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"ReadValueIds\": variables,\n", - " \"AggregateName\": agg_name\n", - " }\n", - "\n", - " for attempt in range(max_retries):\n", - " try:\n", - " content = await request_from_api_async(\n", - " client_pool,\n", - " method=\"POST\",\n", - " endpoint=f\"/values/historicalaggregated\",\n", - " data=json.dumps(body, default=self.json_serial),\n", - " extended_timeout=True\n", - " )\n", - " break\n", - " except (aiohttp.ClientError, ValidationError) as e:\n", - " if attempt < max_retries - 1:\n", - " wait_time = retry_delay * (2 ** attempt)\n", - " logger.warning(f\"Request failed. Retrying in {wait_time} seconds...\")\n", - " await asyncio.sleep(wait_time)\n", - " else:\n", - " logger.error(f\"Max retries reached. Error: {e}\")\n", - " raise RuntimeError(f'Error message {e}')\n", - "\n", - " self._check_content(content)\n", - "\n", - " df_result = pd.json_normalize(\n", - " content, \n", - " record_path=['HistoryReadResults', 'DataValues'], \n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], \n", - " ['HistoryReadResults', 'NodeId','Id'],\n", - " ['HistoryReadResults', 'NodeId','Namespace']]\n", - " )\n", - " batch_id = f\"{time_batch}_{vid}\"\n", - " await data_handler.save_data(batch_id, df_result)\n", - " return batch_id\n", - "\n", - " tasks = [\n", - " process_batch(vid,variables, time_batch)\n", - " for vid,variables in enumerate(variable_batches)\n", - " for time_batch in range(max_time_batches)\n", - " ]\n", - "\n", - " try:\n", - " batch_ids = await asyncio.gather(*tasks)\n", - " # for batch_id in batch_ids:\n", - " # df = await data_handler.get_data(batch_id)\n", - " # all_results.append(df)\n", - "\n", - " # logger.info(\"Combining all batches...\")\n", - " # combined_df = pd.concat(all_results, ignore_index=True)\n", - " # columns = {\n", - " # \"Value.Type\": \"ValueType\",\n", - " # \"Value.Body\": \"Value\",\n", - " # \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " # \"StatusCode.Code\": \"StatusCode\",\n", - " # \"SourceTimestamp\": \"Timestamp\",\n", - " # \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " # \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " # \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " # }\n", - " # return self._process_df(combined_df, columns)\n", - " finally:\n", - " await client_pool.close_all()\n", - " await data_handler.close_all()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical data\n", - "one_day_historical_data = await get_historical_aggregated_values_batch_time_vars_data_async_parquet(\n", - " opc_data,\n", - " start_time=datetime(2024,6,1,00,00),\n", - " end_time=datetime(2024,6,2,00,00),\n", - " pro_interval=pro_interval,\n", - " agg_name=agg_name,\n", - " variable_list=variable_list,\n", - " max_data_points=50000,\n", - " max_concurrent_requests=50\n", - ")\n", - "one_day_historical_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stringset data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_historical_aggregated_values(opc_data,\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " agg_name, \n", - " variable_list\n", - ") -> pd.DataFrame:\n", - " \n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in vars]\n", - "\n", - " body = {\n", - " **opc_data.body, \n", - " \"StartTime\": start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"), \n", - " \"EndTime\": end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"), \n", - " \"ProcessingInterval\": pro_interval, \n", - " \"AggregateName\": agg_name,\n", - " \"ReadValueIds\": extended_variables\n", - " }\n", - " print(body)\n", - "\n", - " content = request_from_api(\n", - " rest_url=opcua_rest_url, \n", - " method=\"POST\", \n", - " endpoint=\"values/historicalaggregated\", \n", - " data=json.dumps(body, default=opc_data.json_serial), \n", - " headers=opc_data.headers, \n", - " extended_timeout=True\n", - " )\n", - " print(content)\n", - " df_result = pd.json_normalize(\n", - " content, \n", - " record_path=['HistoryReadResults', 'DataValues'], \n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], ['HistoryReadResults', 'NodeId','Id'],['HistoryReadResults', 'NodeId','Namespace']\n", - " ]\n", - " )\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " return opc_data._process_df(df_result, columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "start_time=(datetime.datetime.now() - datetime.timedelta(30))\n", - "end_time=(datetime.datetime.now() - datetime.timedelta(29))\n", - "pro_interval=600000\n", - "agg_name=\"Average\"\n", - "variable_list=string_sets.variables_as_list([\"DCPower\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_historical_aggregated_values(opc_data,\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " agg_name, \n", - " variable_list) -> pd.DataFrame:\n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - " batch_size = 100\n", - " batches = [vars[i:i + batch_size] for i in range(0, len(vars), batch_size)]\n", - " \n", - " combined_df = pd.DataFrame() \n", - " \n", - " for batch in batches:\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in batch]\n", - " \n", - " body = {\n", - " **opc_data.body, \n", - " \"StartTime\": start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"), \n", - " \"EndTime\": end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"), \n", - " \"ProcessingInterval\": pro_interval, \n", - " \"AggregateName\": agg_name,\n", - " \"ReadValueIds\": extended_variables\n", - " }\n", - " \n", - " content = request_from_api(\n", - " rest_url=opcua_rest_url, \n", - " method=\"POST\", \n", - " endpoint=\"values/historicalaggregated\", \n", - " data=json.dumps(body, default=opc_data.json_serial), \n", - " headers=opc_data.headers, \n", - " extended_timeout=True\n", - " )\n", - " \n", - " df_result = pd.json_normalize(\n", - " content, \n", - " record_path=['HistoryReadResults', 'DataValues'], \n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], ['HistoryReadResults', 'NodeId','Id'],['HistoryReadResults', 'NodeId','Namespace']]\n", - " )\n", - " \n", - " if combined_df.empty:\n", - " combined_df = df_result\n", - " else:\n", - " combined_df = pd.concat([combined_df, df_result], ignore_index=True)\n", - " \n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - " \n", - " return opc_data._process_df(combined_df, columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "get_historical_aggregated_values(opc_data,\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " agg_name, \n", - " variable_list)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import hashlib\n", - "import concurrent.futures" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_historical_aggregated_values(opc_data, start_time, end_time, pro_interval, agg_name, variable_list) -> pd.DataFrame:\n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - " batch_size = 150\n", - " batches = [vars[i:i + batch_size] for i in range(0, len(vars), batch_size)]\n", - "\n", - " def process_batch(batch):\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in batch]\n", - " body = {\n", - " **opc_data.body,\n", - " \"StartTime\": start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n", - " \"EndTime\": end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"AggregateName\": agg_name,\n", - " \"ReadValueIds\": extended_variables\n", - " }\n", - " content = request_from_api(\n", - " rest_url=opcua_rest_url,\n", - " method=\"POST\",\n", - " endpoint=\"values/historicalaggregated\",\n", - " data=json.dumps(body, default=opc_data.json_serial),\n", - " headers=opc_data.headers,\n", - " extended_timeout=True\n", - " )\n", - " return pd.json_normalize(\n", - " content,\n", - " record_path=['HistoryReadResults', 'DataValues'],\n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], ['HistoryReadResults', 'NodeId', 'Id'], ['HistoryReadResults', 'NodeId', 'Namespace']]\n", - " )\n", - "\n", - " dataframes = []\n", - " with concurrent.futures.ThreadPoolExecutor() as executor:\n", - " future_to_batch = {executor.submit(process_batch, batch): batch for batch in batches}\n", - " for future in concurrent.futures.as_completed(future_to_batch):\n", - " dataframes.append(future.result())\n", - "\n", - " combined_df = pd.concat(dataframes, ignore_index=True) if dataframes else pd.DataFrame()\n", - "\n", - " columns = {\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"IdType\",\n", - " \"HistoryReadResults.NodeId.Id\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " }\n", - "\n", - " return opc_data._process_df(combined_df, columns)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vars = opc_data._get_variable_list_as_list(variable_list)\n", - "extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in vars]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "body = {\n", - " **opc_data.body,\n", - " \"StartTime\": start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n", - " \"EndTime\": end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n", - " \"ProcessingInterval\": pro_interval,\n", - " \"AggregateName\": agg_name,\n", - " \"ReadValueIds\": extended_variables\n", - "}\n", - "body" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "get_historical_aggregated_values(opc_data,\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " agg_name, \n", - " variable_list)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "start_time = datetime.now() - relativedelta(months=1)\n", - "end_time = datetime.now()\n", - "get_historical_aggregated_values(opc_data,\n", - " start_time, \n", - " end_time, \n", - " pro_interval, \n", - " agg_name, \n", - " variable_list)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# History data for 1 day, 10 min aggregate - stringsets\n", - "history_agg = opc_data.get_historical_aggregated_values(\n", - " start_time=(datetime.datetime.now() - datetime.timedelta(30)),\n", - " end_time=(datetime.datetime.now() - datetime.timedelta(29)),\n", - " pro_interval=600000,\n", - " agg_name=\"Average\",\n", - " variable_list=inverters.variables_as_list([\"DCPower\"]),\n", - ")\n", - "history_agg" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import copy\n", - "import math\n", - "from pydantic import BaseModel, AnyUrl\n", - "from datetime import timedelta\n", - "import asyncio\n", - "import aiohttp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class Variables(BaseModel):\n", - " \"\"\"Helper class to parse all values api's.\n", - " Variables are described in https://reference.opcfoundation.org/v104/Core/docs/Part3/8.2.1/\n", - "\n", - " Variables:\n", - " Id: str - Id of the signal, e.g. SSO.EG-AS.WeatherSymbol\n", - " Namespace: int - Namespace on the signal, e.g. 2.\n", - " IdType: int - IdTypes described in https://reference.opcfoundation.org/v104/Core/docs/Part3/8.2.3/.\n", - " \"\"\"\n", - " Id: str\n", - " Namespace: int\n", - " IdType: int" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def make_async_api_request(opc_data, start_time: datetime, end_time: datetime, pro_interval: int, agg_name: str, variable_list: list[Variables]) -> dict:\n", - " \"\"\"Make API request for the given time range and variable list\"\"\"\n", - "\n", - " # Creating a new variable list to remove pydantic models\n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - "\n", - " extended_variables = [\n", - " {\n", - " \"NodeId\": var,\n", - " \"AggregateName\": agg_name,\n", - " }\n", - " for var in vars\n", - " ]\n", - " body = copy.deepcopy(opc_data.body)\n", - " body[\"StartTime\"] = start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"EndTime\"] = end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"ProcessingInterval\"] = pro_interval\n", - " body[\"ReadValueIds\"] = extended_variables\n", - " body[\"AggregateName\"] = agg_name\n", - "\n", - " # Make API request using aiohttp session\n", - " async with aiohttp.ClientSession() as session:\n", - " async with session.post(\n", - " f\"{opcua_rest_url}values/historicalaggregated\",\n", - " data=json.dumps(body, default=opc_data.json_serial),\n", - " headers=opc_data.headers,\n", - " timeout=aiohttp.ClientTimeout(total=None) \n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()\n", - "\n", - " return content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vars = opc_data._get_variable_list_as_list(variable_list)\n", - "vars1 = vars[0:5]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "extended_variables = [\n", - " {\n", - " \"NodeId\": var,\n", - " \"AggregateName\": agg_name,\n", - " }\n", - " for var in vars1\n", - "]\n", - "len(extended_variables)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "body = copy.deepcopy(opc_data.body)\n", - "body[\"StartTime\"] = start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - "body[\"EndTime\"] = end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - "body[\"ProcessingInterval\"] = pro_interval\n", - "body[\"ReadValueIds\"] = extended_variables\n", - "body[\"AggregateName\"] = agg_name\n", - "body" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f\"{opcua_rest_url}values/historicalaggregated\"," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data=json.dumps(body, default=opc_data.json_serial)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_dict = json.loads(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read_value_ids = data_dict['ReadValueIds']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "len(read_value_ids)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "headers=opc_data.headers\n", - "headers" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "timeout=aiohttp.ClientTimeout(total=None) \n", - "timeout" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async with aiohttp.ClientSession() as session:\n", - " async with session.post(\n", - " f\"{opcua_rest_url}values/historicalaggregated\",\n", - " data=json.dumps(body, default=opc_data.json_serial),\n", - " headers=opc_data.headers,\n", - " timeout=aiohttp.ClientTimeout(total=None) \n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_time_batches(start_time: datetime, end_time: datetime, pro_interval: int, batch_size: int) -> list[tuple]:\n", - " \"\"\"Generate time batches based on start time, end time, processing interval, and batch size\"\"\"\n", - "\n", - " total_time_range = end_time - start_time\n", - " pro_interval_seconds = (pro_interval / 1000)\n", - " total_data_points = (total_time_range.total_seconds() // pro_interval_seconds) + 1\n", - "\n", - " total_batches = math.ceil(total_data_points / batch_size)\n", - " actual_batch_size = math.ceil(total_data_points / total_batches)\n", - "\n", - " time_batches = [\n", - " (start_time + timedelta(seconds=(i * actual_batch_size * pro_interval_seconds)),\n", - " start_time + timedelta(seconds=((i + 1) * actual_batch_size * pro_interval_seconds)) - timedelta(seconds=pro_interval_seconds))\n", - " for i in range(total_batches)\n", - " ]\n", - "\n", - " return time_batches" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_variable_batches(variable_list: list[Variables], batch_size: int) -> list[list[Variables]]:\n", - " \"\"\"Generate variable batches based on the variable list and batch size\"\"\"\n", - "\n", - " variable_batches = [\n", - " variable_list[i:i + batch_size] for i in range(0, len(variable_list), batch_size)\n", - " ]\n", - "\n", - " return variable_batches" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def process_api_response(opc_data, response: dict) -> pd.DataFrame:\n", - " \"\"\"Process the API response and return the result dataframe\"\"\"\n", - " \n", - " df_result = pd.json_normalize(response, record_path=['HistoryReadResults', 'DataValues'], \n", - " meta=[['HistoryReadResults', 'NodeId', 'IdType'], ['HistoryReadResults', 'NodeId','Id'],\n", - " ['HistoryReadResults', 'NodeId','Namespace']] )\n", - "\n", - " for i, row in df_result.iterrows():\n", - " if not math.isnan(row[\"Value.Type\"]):\n", - " value_type = opc_data._get_value_type(int(row[\"Value.Type\"])).get(\"type\")\n", - " df_result.at[i, \"Value.Type\"] = str(value_type)\n", - "\n", - " df_result.rename(\n", - " columns={\n", - " \"Value.Type\": \"ValueType\",\n", - " \"Value.Body\": \"Value\",\n", - " \"StatusCode.Symbol\": \"StatusSymbol\",\n", - " \"StatusCode.Code\": \"StatusCode\",\n", - " \"SourceTimestamp\": \"Timestamp\",\n", - " \"HistoryReadResults.NodeId.IdType\": \"Id\",\n", - " \"HistoryReadResults.NodeId.Namespace\": \"Namespace\",\n", - " },\n", - " errors=\"raise\",\n", - " inplace=True,\n", - " )\n", - "\n", - " return df_result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_historical_aggregated_values_async(\n", - " opc_data,\n", - " start_time: datetime,\n", - " end_time: datetime,\n", - " pro_interval: int,\n", - " agg_name: str,\n", - " variable_list: list[Variables],\n", - " batch_size: int = 1000\n", - ") -> pd.DataFrame:\n", - " \"\"\"Request historical aggregated values from the OPC UA server with batching\"\"\"\n", - "\n", - " \n", - " time_batches = generate_time_batches(start_time, end_time, pro_interval, batch_size)\n", - " variable_batches = generate_variable_batches(variable_list, batch_size)\n", - "\n", - " # Creating tasks for each API request and gathering the results\n", - " tasks = []\n", - "\n", - " for time_batch_start, time_batch_end in time_batches:\n", - " for variable_sublist in variable_batches:\n", - " task = asyncio.create_task(\n", - " make_async_api_request(opc_data, time_batch_start, time_batch_end, pro_interval, agg_name, variable_sublist)\n", - " ) \n", - " tasks.append(task)\n", - " \n", - " # Execute all tasks concurrently and gather their results\n", - " responses = await asyncio.gather(*tasks)\n", - " \n", - " # Processing the API responses\n", - " result_list = []\n", - " for idx, batch_response in enumerate(responses):\n", - " \n", - " batch_result = process_api_response(opc_data, batch_response)\n", - " result_list.append(batch_result)\n", - " \n", - " result_df = pd.concat(result_list, ignore_index=True)\n", - "\n", - " return result_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical inverter data in asyncio process\n", - "one_days_historic_inverter_data2 = await get_historical_aggregated_values_async(\n", - " opc_data,\n", - " start_time=(datetime.datetime.now() - datetime.timedelta(30)),\n", - " end_time=(datetime.datetime.now() - datetime.timedelta(29)),\n", - " pro_interval=60*1000,\n", - " agg_name=\"Average\",\n", - " variable_list=string_sets.variables_as_list([\"DCPower\"]),\n", - " batch_size=100\n", - ")\n", - "one_days_historic_inverter_data2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def generate_time_chunks(start_time: datetime, end_time: datetime):\n", - " \"\"\"Generate time chunks between start_time and end_time, each chunk_duration_minutes long.\"\"\"\n", - " delta = timedelta(minutes=60)\n", - " current_time = start_time\n", - " while current_time < end_time:\n", - " chunk_end_time = min(current_time + delta, end_time)\n", - " yield (current_time, chunk_end_time)\n", - " current_time = chunk_end_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def make_async_api_request(opc_data, start_time: datetime, end_time: datetime, pro_interval: int, agg_name: str, variable_list: list[Variables], max_data_points=500) -> dict:\n", - " \"\"\"Make API request for the given time range and variable list, with additional chunking based on data points.\"\"\"\n", - "\n", - " def chunk_list(lst, n):\n", - " \"\"\"Yield successive n-sized chunks from lst.\"\"\"\n", - " for i in range(0, len(lst), n):\n", - " yield lst[i:i + n]\n", - "\n", - " async def fetch_data_for_time_period(session, vars_chunk, start, end):\n", - " \"\"\"Fetch data for a given time period and chunk of variables.\"\"\"\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in vars_chunk]\n", - " body = copy.deepcopy(opc_data.body)\n", - " body[\"StartTime\"] = start.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"EndTime\"] = end.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"ProcessingInterval\"] = pro_interval\n", - " body[\"ReadValueIds\"] = extended_variables\n", - " body[\"AggregateName\"] = agg_name\n", - "\n", - " async with session.post(\n", - " f\"{opcua_rest_url}values/historicalaggregated\",\n", - " data=json.dumps(body, default=str),\n", - " headers=opc_data.headers,\n", - " timeout=aiohttp.ClientTimeout(total=None)\n", - " ) as response:\n", - " response.raise_for_status()\n", - " return await response.json()\n", - "\n", - " # Creating a new variable list to remove pydantic models\n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - " chunk_size = 5 # Chunk size for node IDs\n", - " vars_chunks = list(chunk_list(vars, chunk_size))\n", - "\n", - " all_responses = []\n", - " async with aiohttp.ClientSession() as session:\n", - " for vars_chunk in vars_chunks:\n", - " # Generate time chunks for the given time period\n", - " async for start, end in generate_time_chunks(start_time, end_time):\n", - " content = await fetch_data_for_time_period(session, vars_chunk, start, end)\n", - " all_responses.append(content)\n", - " return all_responses" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def make_async_api_request(opc_data, start_time: datetime, end_time: datetime, pro_interval: int, agg_name: str, variable_list: list[Variables]) -> dict:\n", - " \"\"\"Make API request for the given time range and variable list\"\"\"\n", - "\n", - " def chunk_list(lst, n):\n", - " for i in range(0, len(lst), n):\n", - " yield lst[i:i + n]\n", - "\n", - " # Creating a new variable list to remove pydantic models\n", - " vars = opc_data._get_variable_list_as_list(variable_list)\n", - "\n", - " chunk_size = 150 \n", - " vars_chunks = list(chunk_list(vars, chunk_size))\n", - "\n", - " all_responses = []\n", - " async with aiohttp.ClientSession() as session:\n", - " for vars_chunk in vars_chunks:\n", - " extended_variables = [{\"NodeId\": var, \"AggregateName\": agg_name} for var in vars_chunk]\n", - " body = copy.deepcopy(opc_data.body)\n", - " body[\"StartTime\"] = start_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"EndTime\"] = end_time.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", - " body[\"ProcessingInterval\"] = pro_interval\n", - " body[\"ReadValueIds\"] = extended_variables\n", - " body[\"AggregateName\"] = agg_name\n", - "\n", - " async with session.post(\n", - " f\"{opcua_rest_url}values/historicalaggregated\",\n", - " data=json.dumps(body, default=str),\n", - " headers=opc_data.headers,\n", - " timeout=aiohttp.ClientTimeout(total=None)\n", - " ) as response:\n", - " response.raise_for_status()\n", - " content = await response.json()\n", - " all_responses.append(content) \n", - "\n", - " return all_responses" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime, timedelta\n", - "from typing import List, Tuple" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_time_chunks(start_time: datetime, end_time: datetime, interval_hours: int) -> List[Tuple[datetime, datetime]]:\n", - " \"\"\"Generate time chunks within the given start and end time with specified interval in hours.\"\"\"\n", - " delta = timedelta(hours=interval_hours)\n", - " current_time = start_time\n", - " chunks = []\n", - "\n", - " while current_time < end_time:\n", - " chunk_end_time = min(current_time + delta, end_time) \n", - " chunks.append((current_time, chunk_end_time))\n", - " current_time += delta\n", - "\n", - " return chunks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 1 day aggregated historical inverter data in asyncio process\n", - "one_days_historic_inverter_data2 = await make_async_api_request(\n", - " opc_data,\n", - " start_time=(datetime.datetime.now() - datetime.timedelta(30)),\n", - " end_time=(datetime.datetime.now() - datetime.timedelta(29)),\n", - " pro_interval=60*1000,\n", - " agg_name=\"Average\",\n", - " variable_list=string_sets.variables_as_list([\"DCPower\"])\n", - ")\n", - "one_days_historic_inverter_data2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.12.1 64-bit", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.4" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "6b866f0bc560289bf4bb2415ae9074243764eb008c10d00a1da29433677418de" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tests/analytics_helper_test.py b/tests/analytics_helper_test.py index 21362af..0e45328 100644 --- a/tests/analytics_helper_test.py +++ b/tests/analytics_helper_test.py @@ -69,102 +69,110 @@ ] -# Our test case class -class AnalyticsHelperTestCase(unittest.TestCase): - def test_analytics_helper_initialization_success(self): - result = AnalyticsHelper(proper_json) - assert isinstance(result.dataframe, pd.DataFrame) - assert "Subtype" not in result.dataframe - assert "ObjectId" not in result.dataframe - assert "ObjectName" not in result.dataframe - assert result.list_of_ids() == ["id1"] - assert result.list_of_names() == ["SomeName"] - assert result.list_of_types() == ["SomeType"] - assert "SomeName" in result.list_of_variable_names() - assert "SomeName2" in result.list_of_variable_names() - assert "Property" in result.properties_as_dataframe() +@pytest.fixture +def proper_helper(): + return AnalyticsHelper(proper_json) + + +@pytest.fixture +def descendant_helper(): + return AnalyticsHelper(descendant_json) + + +@pytest.fixture +def ancestor_helper(): + return AnalyticsHelper(ancestor_json) + + +@pytest.fixture +def faulty_helper(): + return AnalyticsHelper(faulty_json) + + +class TestCaseAnalyticsHelper: + def test_analytics_helper_initialization_success(self, proper_helper): + assert isinstance(proper_helper.dataframe, pd.DataFrame) + assert "Subtype" not in proper_helper.dataframe + assert "ObjectId" not in proper_helper.dataframe + assert "ObjectName" not in proper_helper.dataframe + assert proper_helper.list_of_ids() == ["id1"] + assert proper_helper.list_of_names() == ["SomeName"] + assert proper_helper.list_of_types() == ["SomeType"] + assert "SomeName" in proper_helper.list_of_variable_names() + assert "SomeName2" in proper_helper.list_of_variable_names() + assert "Property" in proper_helper.properties_as_dataframe() assert ( "Property1" - in result.properties_as_dataframe()["Property"].to_list() + in proper_helper.properties_as_dataframe()["Property"].to_list() ) - assert "VariableName" in result.variables_as_dataframe() + assert "VariableName" in proper_helper.variables_as_dataframe() assert ( "SomeName" - in result.variables_as_dataframe()["VariableName"].to_list() + in proper_helper.variables_as_dataframe()["VariableName"].to_list() ) - def test_split_id_success(self): - instance = AnalyticsHelper(proper_json) - result = instance.split_id("1:2:TEXT") + def test_split_id_success(self, proper_helper): + result = proper_helper.split_id("1:2:TEXT") assert result["Id"] == "TEXT" assert result["Namespace"] == 1 assert result["IdType"] == 2 - def test_split_id_failure(self): - result = AnalyticsHelper(proper_json) + def test_split_id_failure(self, proper_helper): with pytest.raises(ValueError): - result.split_id("TEXT:TEXT:TEXT") - - def test_analytics_helper_descendants_success(self): - result = AnalyticsHelper(descendant_json) - assert isinstance(result.dataframe, pd.DataFrame) - assert "DescendantId" not in result.dataframe - assert "DescendantType" not in result.dataframe - assert "DescendantName" not in result.dataframe - - def test_analytics_helper_ancestor_success(self): - result = AnalyticsHelper(ancestor_json) - assert isinstance(result.dataframe, pd.DataFrame) - assert "AncestorId" not in result.dataframe - assert "AncestorType" not in result.dataframe - assert "AncestorName" not in result.dataframe - - def test_analytics_helper_missing_vars_list(self): - result = AnalyticsHelper(ancestor_json) - assert result.list_of_variable_names() == [] - - def test_analytics_helper_missing_props(self): - result = AnalyticsHelper(ancestor_json) - assert result.properties_as_dataframe() is None - - def test_analytics_helper_missing_vars(self): - result = AnalyticsHelper(ancestor_json) - assert result.variables_as_dataframe() is None - - def test_analytics_helper_failure(self): - result = AnalyticsHelper(faulty_json) - assert result.dataframe is None - assert result.list_of_ids() == [] - assert result.list_of_names() == [] - assert result.list_of_types() == [] - assert result.list_of_variable_names() == [] - assert result.properties_as_dataframe() is None - assert result.variables_as_dataframe() is None - - def test_analytics_helper_variables_as_list(self): - result = AnalyticsHelper(proper_json) - ids = result.variables_as_list() + proper_helper.split_id("TEXT:TEXT:TEXT") + + def test_analytics_helper_descendants_success(self, descendant_helper): + assert isinstance(descendant_helper.dataframe, pd.DataFrame) + assert "DescendantId" not in descendant_helper.dataframe + assert "DescendantType" not in descendant_helper.dataframe + assert "DescendantName" not in descendant_helper.dataframe + + def test_analytics_helper_ancestor_success(self, ancestor_helper): + assert isinstance(ancestor_helper.dataframe, pd.DataFrame) + assert "AncestorId" not in ancestor_helper.dataframe + assert "AncestorType" not in ancestor_helper.dataframe + assert "AncestorName" not in ancestor_helper.dataframe + + def test_analytics_helper_missing_vars_list(self, ancestor_helper): + assert ancestor_helper.list_of_variable_names() == [] + + def test_analytics_helper_missing_props(self, ancestor_helper): + assert ancestor_helper.properties_as_dataframe() is None + + def test_analytics_helper_missing_vars(self, ancestor_helper): + assert ancestor_helper.variables_as_dataframe() is None + + def test_analytics_helper_failure(self, faulty_helper): + assert faulty_helper.dataframe is None + assert faulty_helper.list_of_ids() == [] + assert faulty_helper.list_of_names() == [] + assert faulty_helper.list_of_types() == [] + assert faulty_helper.list_of_variable_names() == [] + assert faulty_helper.properties_as_dataframe() is None + assert faulty_helper.variables_as_dataframe() is None + + def test_analytics_helper_variables_as_list(self, proper_helper): + ids = proper_helper.variables_as_list() assert "Id" in ids[0].keys() assert "Namespace" in ids[0].keys() assert "IdType" in ids[0].keys() assert len(ids) == 2 - def test_analytics_helper_variables_as_list_with_include_only(self): - result = AnalyticsHelper(proper_json) - ids = result.variables_as_list(include_only=["SomeName2"]) + def test_analytics_helper_variables_as_list_with_include_only( + self, proper_helper + ): + ids = proper_helper.variables_as_list(include_only=["SomeName2"]) assert len(ids) == 1 - ids2 = result.variables_as_list(include_only=["SomeRandomName"]) + ids2 = proper_helper.variables_as_list(include_only=["SomeRandomName"]) assert len(ids2) == 0 - def test_namespaces_as_list_successful(self): - result = AnalyticsHelper(proper_json) - nslist = result.namespaces_as_list(namespace_array) + def test_namespaces_as_list_successful(self, proper_helper): + nslist = proper_helper.namespaces_as_list(namespace_array) assert len(nslist) == 7 assert nslist[0] == "http://opcfoundation.org/UA/" - def test_namespaces_as_list_empty(self): - result = AnalyticsHelper(proper_json) - nslist = result.namespaces_as_list(["CrappyItem"]) + def test_namespaces_as_list_empty(self, proper_helper): + nslist = proper_helper.namespaces_as_list(["CrappyItem"]) assert len(nslist) == 0 diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 3d64c53..3d3abbb 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -1,6 +1,5 @@ import unittest -from unittest import mock -from unittest.mock import patch +from unittest.mock import patch, Mock import pytest from pydantic import ValidationError, BaseModel, AnyUrl from copy import deepcopy @@ -279,14 +278,13 @@ class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data self.status_code = status_code - self.raise_for_status = mock.Mock(return_value=False) + self.raise_for_status = Mock(return_value=False) self.headers = {"Content-Type": "application/json"} def json(self): return self.json_data -# This method will be used by the mock to replace requests def successful_self_service_mocked_requests(*args, **kwargs): if args[0] == f"{URL}self-service/login/api": return MockResponse(successfull_self_service_login_response, 200) @@ -354,21 +352,15 @@ class AnyUrlModel(BaseModel): url: AnyUrl -# Our test case class -class TestCaseAuthClient(unittest.TestCase): +class TestCaseAuthClient: - def test_init(self): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) - self.assertEqual(auth_client.username, username) - self.assertEqual(auth_client.password, password) - self.assertIsNone(auth_client.id) - self.assertIsNone(auth_client.token) - self.assertEqual( - auth_client.headers, {"Content-Type": "application/json"} - ) - self.assertIsInstance(auth_client.session, requests.Session) + def test_init(self, auth_client): + assert auth_client.username == username + assert auth_client.password == password + assert auth_client.id is None + assert auth_client.token is None + assert auth_client.headers == {"Content-Type": "application/json"} + assert isinstance(auth_client.session, requests.Session) def test_init_with_trailing_slash(self): url_with_trailing_slash = URL.rstrip("/") + "/" @@ -392,101 +384,80 @@ def test_malformed_rest_url(self): with pytest.raises(ValidationError): AnyUrlModel(rest_url="invalid-url") - @mock.patch( + @patch( "requests.get", side_effect=successful_self_service_mocked_requests ) - def test_get_self_service_login_id_successful(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_id_successful(self, mock_get, auth_client): auth_client.get_login_id() assert auth_client.id == auth_id - @mock.patch( + @patch( "requests.get", side_effect=unsuccessful_self_service_login_mocked_requests, ) - def test_get_self_service_login_id_unsuccessful(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_id_unsuccessful( + self, mock_get, auth_client + ): with pytest.raises(RuntimeError): auth_client.get_login_id() - @mock.patch("requests.get", side_effect=empty_self_service_mocked_requests) - def test_get_self_service_login_id_empty(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + @patch("requests.get", side_effect=empty_self_service_mocked_requests) + def test_get_self_service_login_id_empty(self, mock_get, auth_client): with pytest.raises(RuntimeError): auth_client.get_login_id() - @mock.patch( + @patch( "requests.get", side_effect=wrong_id_self_service_mocked_requests ) - def test_get_self_service_login_id_wrong_id(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_id_wrong_id(self, mock_get, auth_client): with pytest.raises(RuntimeError): auth_client.get_login_id() @patch("pyprediktormapclient.auth_client.request_from_api") - def test_get_login_id_no_error_message(self, mock_request): + def test_get_login_id_no_error_message(self, mock_request, auth_client): mock_request.return_value = {"error": "Invalid request"} - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) - with self.assertRaises(RuntimeError) as context: + with pytest.raises(RuntimeError) as context: auth_client.get_login_id() - self.assertEqual(str(context.exception), "Invalid request") + assert str(context.value) == "Invalid request" - @mock.patch( + @patch( "requests.post", side_effect=empty_self_service_login_token_mocked_requests, ) - def test_get_self_service_login_token_empty(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_token_empty(self, mock_get, auth_client): auth_client.id = auth_id with pytest.raises(RuntimeError): auth_client.get_login_token() - @mock.patch( + @patch( "requests.post", side_effect=successful_self_service_login_token_mocked_requests, ) - def test_get_self_service_login_token_successful(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_token_successful( + self, mock_get, auth_client + ): auth_client.id = auth_id auth_client.get_login_token() - self.assertIsNotNone(auth_client.token) - self.assertEqual(auth_client.token.session_token, auth_session_id) + assert auth_client.token is not None + assert auth_client.token.session_token == auth_session_id expected_expires_at = Token.remove_nanoseconds(auth_expires_at) - self.assertEqual(auth_client.token.expires_at, expected_expires_at) + assert auth_client.token.expires_at == expected_expires_at - @mock.patch( + @patch( "requests.post", side_effect=unsuccessful_self_service_login_token_mocked_requests, ) - def test_get_self_service_login_token_unsuccessful(self, mock_get): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_login_token_unsuccessful( + self, mock_get, auth_client + ): auth_client.id = auth_id with pytest.raises(RuntimeError): auth_client.get_login_token() - def test_get_self_service_token_expired(self): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_token_expired(self, auth_client): auth_client.token = Token( session_token=auth_session_id, expires_at=auth_expires_at_2hrs_ago ) @@ -497,41 +468,36 @@ def test_get_self_service_token_expired(self): assert token_expired @patch("pyprediktormapclient.auth_client.request_from_api") - def test_get_login_token_success_no_expiry(self, mock_request): + def test_get_login_token_success_no_expiry( + self, mock_request, auth_client + ): mock_response = { "Success": True, "session_token": "some_token", "session": {}, } mock_request.return_value = mock_response - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) auth_client.id = "some_id" auth_client.get_login_token() - self.assertEqual(auth_client.token.session_token, "some_token") - self.assertIsNone(auth_client.token.expires_at) + assert auth_client.token.session_token == "some_token" + assert auth_client.token.expires_at is None @patch("pyprediktormapclient.auth_client.request_from_api") - def test_get_login_token_invalid_expiry_format(self, mock_request): + def test_get_login_token_invalid_expiry_format( + self, mock_request, auth_client + ): mock_response = { "Success": True, "session_token": "some_token", "session": {"expires_at": "invalid_date_format"}, } mock_request.return_value = mock_response - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) auth_client.id = "some_id" auth_client.get_login_token() - self.assertEqual(auth_client.token.session_token, "some_token") - self.assertIsNone(auth_client.token.expires_at) + assert auth_client.token.session_token == "some_token" + assert auth_client.token.expires_at is None - def test_get_self_service_token_expired_none(self): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_get_self_service_token_expired_none(self, auth_client): auth_client.token = Token(session_token=auth_session_id) token_expired = auth_client.check_if_token_has_expired() assert token_expired @@ -539,35 +505,26 @@ def test_get_self_service_token_expired_none(self): @patch.object(AUTH_CLIENT, "get_login_id") @patch.object(AUTH_CLIENT, "get_login_token") def test_request_new_ory_token( - self, mock_get_login_token, mock_get_login_id + self, mock_get_login_token, mock_get_login_id, auth_client ): - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) auth_client.request_new_ory_token() mock_get_login_id.assert_called_once() mock_get_login_token.assert_called_once() def test_remove_nanoseconds_validator(self): - self.assertIsNone(Token.remove_nanoseconds(None)) + assert Token.remove_nanoseconds(None) is None dt = datetime.now() - self.assertEqual(Token.remove_nanoseconds(dt), dt) + assert Token.remove_nanoseconds(dt) == dt expected_dt = datetime( 2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc ) - self.assertEqual( - Token.remove_nanoseconds(auth_expires_at), expected_dt - ) + assert Token.remove_nanoseconds(auth_expires_at) == expected_dt invalid_str = "2023-05-01 12:34:56" - self.assertEqual(Token.remove_nanoseconds(invalid_str), invalid_str) - - def test_token_expires_at_handling(self): + assert Token.remove_nanoseconds(invalid_str) == invalid_str - auth_client = AUTH_CLIENT( - rest_url=URL, username=username, password=password - ) + def test_token_expires_at_handling(self, auth_client): auth_expires_at_datetime = datetime( 2022, 12, 4, 7, 31, 28, 767407, tzinfo=timezone.utc ) diff --git a/tests/conftest.py b/tests/conftest.py index c0c30b9..840d105 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,18 +1,82 @@ import pytest -from unittest.mock import AsyncMock +import random +import string +from unittest.mock import Mock, patch, AsyncMock from aiohttp import ClientResponseError from pyprediktormapclient.opc_ua import OPC_UA +from pyprediktormapclient.dwh.dwh import DWH +from pyprediktormapclient.dwh.db import Db +from pyprediktormapclient.auth_client import AUTH_CLIENT +from pyprediktormapclient.model_index import ModelIndex URL = "http://someserver.somedomain.com/v1/" +username = "some@user.com" +password = "somepassword" OPC_URL = "opc.tcp://nosuchserver.nosuchdomain.com" +def grs(): + """Generate a random string.""" + return "".join( + random.choices(string.ascii_uppercase + string.digits, k=10) + ) + + @pytest.fixture def opc(): opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) return opc +@pytest.fixture +def auth_client(): + return AUTH_CLIENT(rest_url=URL, username=username, password=password) + + +@pytest.fixture +def mock_pyodbc_connect(monkeypatch): + mock_connection = Mock() + mock_cursor = Mock() + mock_connection.cursor.return_value = mock_cursor + monkeypatch.setattr( + "pyodbc.connect", lambda *args, **kwargs: mock_connection + ) + return mock_cursor + + +@pytest.fixture +def mock_pyodbc_drivers(monkeypatch): + monkeypatch.setattr( + "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] + ) + + +@pytest.fixture +def mock_get_drivers(monkeypatch): + monkeypatch.setattr( + Db, + "_Db__get_list_of_available_and_supported_pyodbc_drivers", + lambda self: ["Driver1"], + ) + + +@pytest.fixture +def dwh_instance(mock_pyodbc_connect, mock_pyodbc_drivers): + with patch.object(DWH, "_DWH__initialize_context_services"): + return DWH(grs(), grs(), grs(), grs()) + + +@pytest.fixture +def mock_iter_modules(): + with patch("pkgutil.iter_modules") as mock_iter_modules: + yield mock_iter_modules + + +@pytest.fixture +def db_instance(mock_pyodbc_connect, mock_pyodbc_drivers): + return Db(grs(), grs(), grs(), grs()) + + @pytest.fixture def mock_error_response(): response = AsyncMock() diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index fc224fd..6a32424 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -1,10 +1,8 @@ import pytest -import random -import string import pyodbc import inspect -import logging import pandas as pd +from conftest import grs from unittest.mock import Mock, patch from typing import List, Any, get_origin, get_args from pyprediktormapclient.dwh.db import Db @@ -13,81 +11,6 @@ class TestCaseDB: - @staticmethod - def grs(): - """Generate a random string.""" - return "".join( - random.choices(string.ascii_uppercase + string.digits, k=10) - ) - - @pytest.fixture - def mock_pyodbc_connect(self, monkeypatch): - mock_connection = Mock() - mock_cursor = Mock() - mock_connection.cursor.return_value = mock_cursor - monkeypatch.setattr( - "pyodbc.connect", lambda *args, **kwargs: mock_connection - ) - return mock_cursor - - @pytest.fixture - def mock_pyodbc_drivers(self, monkeypatch): - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - @pytest.fixture - def mock_get_drivers(self, monkeypatch): - monkeypatch.setattr( - Db, - "_Db__get_list_of_available_and_supported_pyodbc_drivers", - lambda self: ["Driver1"], - ) - - @pytest.fixture - def db_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): - return Db(self.grs(), self.grs(), self.grs(), self.grs()) - - @pytest.mark.parametrize( - "error, expected_error, expected_log_message", - [ - ( - pyodbc.DataError("Error code", "Error message"), - pyodbc.DataError, - "DataError Error code: Error message", - ), - ( - pyodbc.DatabaseError("Error code", "Error message"), - pyodbc.Error, - "DatabaseError Error code: Error message", - ), - ], - ) - def test_init_connection_error( - self, - monkeypatch, - error, - expected_error, - expected_log_message, - caplog, - mock_get_drivers, - ): - monkeypatch.setattr("pyodbc.connect", Mock(side_effect=error)) - with pytest.raises(expected_error) as exc_info: - with caplog.at_level(logging.ERROR): - Db(self.grs(), self.grs(), self.grs(), self.grs()) - - if expected_error == pyodbc.Error: - assert str(exc_info.value) == "Failed to connect to the database" - else: - assert str(exc_info.value) == str(error) - - assert expected_log_message in caplog.text - if expected_error == pyodbc.Error: - assert ( - "Failed to connect to the DataWarehouse after 3 attempts" - in caplog.text - ) def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exception( self, monkeypatch @@ -103,7 +26,7 @@ def test_init_when_instantiate_db_but_no_pyodbc_drivers_available_then_throw_exc with pytest.raises( ValueError, match="No supported ODBC drivers found." ): - Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + Db(grs(), grs(), grs(), grs(), driver_index) def test_init_with_out_of_range_driver_index(self, monkeypatch): driver_index = 1 @@ -117,7 +40,7 @@ def test_init_with_out_of_range_driver_index(self, monkeypatch): with pytest.raises( ValueError, match="Driver index 1 is out of range." ): - Db(self.grs(), self.grs(), self.grs(), self.grs(), driver_index) + Db(grs(), grs(), grs(), grs(), driver_index) def test_context_manager_enter(self, db_instance): assert db_instance.__enter__() == db_instance diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index 3ea12e4..9e029b0 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -1,99 +1,22 @@ import pytest import unittest -import random import inspect -import string -import pyodbc -import logging import datetime from typing import Dict -from unittest.mock import Mock, patch, MagicMock, call +from unittest.mock import patch, MagicMock, call from pyprediktormapclient.dwh.dwh import DWH from pyprediktormapclient.dwh.idwh import IDWH class TestCaseDWH: - @staticmethod - def grs(): - """Generate a random string.""" - return "".join( - random.choices(string.ascii_uppercase + string.digits, k=10) - ) - - @pytest.fixture - def mock_pyodbc_connect(self): - with patch("pyodbc.connect") as mock_connect: - mock_connection = Mock() - mock_cursor = Mock() - mock_connection.cursor.return_value = mock_cursor - mock_connect.return_value = mock_connection - yield mock_connect - - @pytest.fixture - def mock_pyodbc_drivers(self, monkeypatch): - monkeypatch.setattr( - "pyodbc.drivers", lambda: ["Driver1", "Driver2", "Driver3"] - ) - - @pytest.fixture - def mock_get_drivers(self, monkeypatch): - monkeypatch.setattr( - "pyprediktorutilities.dwh.dwh.Db._Db__get_list_of_available_and_supported_pyodbc_drivers", - lambda self: ["Driver1"], - ) - @pytest.fixture - def dwh_instance(self, mock_pyodbc_connect, mock_pyodbc_drivers): - with patch.object(DWH, "_DWH__initialize_context_services"): - return DWH(self.grs(), self.grs(), self.grs(), self.grs()) + class TestService: + def __init__(self, dwh): + self.dwh = dwh - @pytest.fixture - def mock_iter_modules(self): - with patch("pkgutil.iter_modules") as mock_iter_modules: - yield mock_iter_modules - - @patch.object(DWH, "_DWH__initialize_context_services") - @patch("pyprediktormapclient.dwh.dwh.Db.__init__") - def test_init( - self, - mock_db_init, - mock_initialize_context_services, - mock_pyodbc_connect, + def test_init_default_driver( + self, mock_pyodbc_drivers, mock_pyodbc_connect ): - DWH("test_url", "test_db", "test_user", "test_pass", -1) - mock_db_init.assert_called_once_with( - "test_url", "test_db", "test_user", "test_pass", -1 - ) - mock_initialize_context_services.assert_called_once() - - @patch("pyprediktorutilities.dwh.dwh.pyodbc.connect") - def test_init_connection_error(self, mock_connect): - mock_connect.side_effect = pyodbc.DataError( - "Error code", "Error message" - ) - with pytest.raises(pyodbc.DataError): - DWH("test_url", "test_db", "test_user", "test_pass", 0) - - @patch("pyprediktorutilities.dwh.dwh.pyodbc.connect") - def test_init_connection_retry(self, mock_connect, caplog): - mock_connect.side_effect = [ - pyodbc.DatabaseError("Error code", "Temporary error message"), - pyodbc.DatabaseError("Error code", "Temporary error message"), - pyodbc.DatabaseError("Error code", "Permanent error message"), - ] - with caplog.at_level(logging.ERROR): - with pytest.raises(pyodbc.DatabaseError): - DWH("test_url", "test_db", "test_user", "test_pass", 0) - assert ( - "Failed to connect to the DataWarehouse after 3 attempts." - in caplog.text - ) - - @patch("pyodbc.connect") - @patch("pyodbc.drivers") - def test_init_default_driver(self, mock_drivers, mock_connect): - mock_drivers.return_value = ["Driver1", "Driver2"] - mock_connect.return_value = Mock() dwh = DWH("test_url", "test_db", "test_user", "test_pass") assert dwh.driver == "Driver1" @@ -151,9 +74,8 @@ def test_version_without_results(self, mock_fetch, dwh_instance): "SET NOCOUNT ON; EXEC [dbo].[GetVersion]" ) - @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") - def test_initialize_context_services( - self, mock_import_module, mock_iter_modules, dwh_instance + def setup_mock_imports( + self, mock_iter_modules, mock_import_module, dwh_instance ): mock_iter_modules.return_value = [ (None, "pyprediktormapclient.dwh.context.enercast", False), @@ -161,22 +83,22 @@ def test_initialize_context_services( (None, "pyprediktormapclient.dwh.context.solcast", False), ] - class TestService: - def __init__(self, dwh): - self.dwh = dwh - def mock_import(name): mock_module = MagicMock() mock_module.__dir__ = lambda *args: ["TestService"] - mock_module.TestService = TestService + mock_module.TestService = self.TestService return mock_module mock_import_module.side_effect = mock_import + dwh_instance._DWH__initialize_context_services() - with patch.object( - dwh_instance, "_is_attr_valid_service_class", return_value=True - ): - dwh_instance._DWH__initialize_context_services() + @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") + def test_initialize_context_services( + self, mock_import_module, mock_iter_modules, dwh_instance + ): + self.setup_mock_imports( + mock_iter_modules, mock_import_module, dwh_instance + ) expected_calls = [ call("pyprediktormapclient.dwh.context.enercast"), @@ -192,31 +114,16 @@ def mock_import(name): assert hasattr(dwh_instance, "solcast") for attr in ["enercast", "plant", "solcast"]: - assert isinstance(getattr(dwh_instance, attr), TestService) + assert isinstance(getattr(dwh_instance, attr), self.TestService) assert getattr(dwh_instance, attr).dwh == dwh_instance @patch("pyprediktormapclient.dwh.dwh.importlib.import_module") def test_initialize_context_services_with_modules( self, mock_import_module, mock_iter_modules, dwh_instance ): - mock_iter_modules.return_value = [ - (None, "pyprediktormapclient.dwh.context.enercast", False), - (None, "pyprediktormapclient.dwh.context.plant", False), - (None, "pyprediktormapclient.dwh.context.solcast", False), - ] - - class TestService: - def __init__(self, dwh): - self.dwh = dwh - - def mock_import(name): - mock_module = MagicMock() - mock_module.__dir__ = lambda *args: ["TestService"] - mock_module.TestService = TestService - return mock_module - - mock_import_module.side_effect = mock_import - dwh_instance._DWH__initialize_context_services() + self.setup_mock_imports( + mock_iter_modules, mock_import_module, dwh_instance + ) expected_modules = [ "pyprediktormapclient.dwh.context.enercast", @@ -238,7 +145,7 @@ def mock_import(name): for attr in ["enercast", "plant", "solcast"]: assert isinstance( - getattr(dwh_instance, attr), TestService + getattr(dwh_instance, attr), self.TestService ), f"{attr} is not an instance of TestService" assert ( getattr(dwh_instance, attr).dwh == dwh_instance diff --git a/tests/model_index_test.py b/tests/model_index_test.py index 9b2f5c4..7cdc1f0 100644 --- a/tests/model_index_test.py +++ b/tests/model_index_test.py @@ -1,11 +1,12 @@ import pytest import unittest -from unittest import mock +from unittest.mock import Mock, patch from pydantic import ValidationError, BaseModel, AnyUrl from pyprediktormapclient.model_index import ModelIndex from datetime import datetime, date from pydantic_core import Url import requests +from urllib.parse import urlparse URL = "http://someserver.somedomain.com/v1/" object_types = [ @@ -61,7 +62,6 @@ ] -# This method will be used by the mock to replace requests.get def mocked_requests(*args, **kwargs): class MockResponse: def __init__(self, json_data, status_code): @@ -76,6 +76,11 @@ def raise_for_status(self): if self.status_code >= 400: raise requests.HTTPError(f"{self.status_code} Client Error") + # Check if the URL is malformed + parsed_url = urlparse(args[0]) + if not parsed_url.scheme: + raise requests.exceptions.MissingSchema("Invalid URL 'No_valid_url': No scheme supplied. Perhaps you meant http://No_valid_url?") + if args[0] == f"{URL}query/object-types": return MockResponse(object_types, 200) elif args[0] == f"{URL}query/namespace-array": @@ -89,33 +94,37 @@ def raise_for_status(self): return MockResponse(None, 404) +requests.get = Mock(side_effect=mocked_requests) +requests.post = Mock(side_effect=mocked_requests) + +@pytest.fixture +def model_index(): + return ModelIndex(url=URL) class AnyUrlModel(BaseModel): url: AnyUrl -class TestCaseModelIndex(unittest.TestCase): +class TestCaseModelIndex: - @mock.patch("requests.get", side_effect=mocked_requests) - @mock.patch("pyprediktormapclient.model_index.ModelIndex.get_object_types") - def test_init_variations(self, mock_get_object_types, mock_get): + @patch("pyprediktormapclient.model_index.ModelIndex.get_object_types") + def test_init_variations(self, mock_get_object_types, model_index, mock_auth_client): mock_get_object_types.return_value = object_types # Without auth_client - model = ModelIndex(url=URL) - assert "Authorization" not in model.headers - assert "Cookie" not in model.headers + assert "Authorization" not in model_index.headers + assert "Cookie" not in model_index.headers # With auth_client, no token, no session_token - auth_client = mock.Mock(spec=[]) + auth_client = Mock(spec=[]) auth_client.token = None model = ModelIndex(url=URL, auth_client=auth_client) assert "Authorization" not in model.headers assert "Cookie" not in model.headers # With auth_client, token, and session_token - auth_client = mock.Mock() - auth_client.token = mock.Mock() + auth_client = Mock() + auth_client.token = Mock() auth_client.token.session_token = "test_token" auth_client.session_token = "ory_session_token" model = ModelIndex(url=URL, auth_client=auth_client) @@ -141,123 +150,104 @@ def test_malformed_url(self): with pytest.raises(ValidationError): AnyUrlModel(url="not_an_url") - @mock.patch("requests.get", side_effect=mocked_requests) - def test_json_serial(self, mock_get): - model = ModelIndex(url=URL) + def test_json_serial(self, model_index): dt = datetime(2023, 1, 1, 12, 0, 0) - assert model.json_serial(dt) == "2023-01-01T12:00:00" + assert model_index.json_serial(dt) == "2023-01-01T12:00:00" d = date(2023, 1, 1) - assert model.json_serial(d) == "2023-01-01" + assert model_index.json_serial(d) == "2023-01-01" url = Url("http://example.com") - assert model.json_serial(url) == "http://example.com/" + assert model_index.json_serial(url) == "http://example.com/" with pytest.raises(TypeError): - model.json_serial(set()) + model_index.json_serial(set()) - @mock.patch("requests.get", side_effect=mocked_requests) - def test_check_auth_client(self, mock_get): - model = ModelIndex(url=URL) - model.auth_client = mock.Mock() + def test_check_auth_client(self, model_index): + model_index.auth_client = Mock() - model.auth_client.token = mock.Mock() - model.auth_client.token.session_token = "new_token" + model_index.auth_client.token = Mock() + model_index.auth_client.token.session_token = "new_token" content = {"error": {"code": 404}} - model.check_auth_client(content) - model.auth_client.request_new_ory_token.assert_called_once() - assert model.headers["Authorization"] == "Bearer new_token" + model_index.check_auth_client(content) + model_index.auth_client.request_new_ory_token.assert_called_once() + assert model_index.headers["Authorization"] == "Bearer new_token" - model.auth_client.token = None - model.auth_client.request_new_ory_token = mock.Mock() + model_index.auth_client.token = None + model_index.auth_client.request_new_ory_token = Mock() def side_effect(): - model.auth_client.token = mock.Mock() - model.auth_client.token.session_token = "new_token" + model_index.auth_client.token = Mock() + model_index.auth_client.token.session_token = "new_token" - model.auth_client.request_new_ory_token.side_effect = side_effect - model.check_auth_client(content) - model.auth_client.request_new_ory_token.assert_called_once() - assert model.headers["Authorization"] == "Bearer new_token" + model_index.auth_client.request_new_ory_token.side_effect = side_effect + model_index.check_auth_client(content) + model_index.auth_client.request_new_ory_token.assert_called_once() + assert model_index.headers["Authorization"] == "Bearer new_token" content = {"ErrorMessage": "Some error"} with pytest.raises(RuntimeError, match="Some error"): - model.check_auth_client(content) + model_index.check_auth_client(content) - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_namespace_array(self, mock_get): - model = ModelIndex(url=URL) - result = model.get_namespace_array() + def test_get_namespace_array(self, model_index): + result = model_index.get_namespace_array() assert result == namespaces - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_types(self, mock_get): - model = ModelIndex(url=URL) - result = model.get_object_types() + def test_get_object_types(self, model_index): + result = model_index.get_object_types() assert result == object_types - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_type_id_from_name(self, mock_get): - model = ModelIndex(url=URL) + def test_get_object_type_id_from_name(self, model_index): assert ( - model.get_object_type_id_from_name("IPVBaseCalculate") + model_index.get_object_type_id_from_name("IPVBaseCalculate") == "6:0:1029" ) - assert model.get_object_type_id_from_name("NonExistentType") is None - - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_objects_of_type(self, mock_get): - model = ModelIndex(url=URL) - with mock.patch("requests.post", side_effect=mocked_requests): - assert ( - model.get_objects_of_type(type_name="IPVBaseCalculate") - == objects_of_type - ) - assert ( - model.get_objects_of_type(type_name="IPVBaseCalculate2") - is None - ) + assert model_index.get_object_type_id_from_name("NonExistentType") is None - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_descendants(self, mock_get): - model = ModelIndex(url=URL) - with mock.patch("requests.post", side_effect=mocked_requests): - result = model.get_object_descendants( - type_name="IPVBaseCalculate", - ids=["Anything"], - domain="PV_Assets", - ) - assert result == descendants + def test_get_objects_of_type(self, model_index): + assert ( + model_index.get_objects_of_type(type_name="IPVBaseCalculate") + == objects_of_type + ) + assert ( + model_index.get_objects_of_type(type_name="IPVBaseCalculate2") + is None + ) - with self.assertRaises(ValueError): - model.get_object_descendants( + def test_get_object_descendants(self, model_index): + result = model_index.get_object_descendants( + type_name="IPVBaseCalculate", + ids=["Anything"], + domain="PV_Assets", + ) + assert result == descendants + + with pytest.raises(ValueError): + model_index.get_object_descendants( type_name=None, ids=["Anything"], domain="PV_Assets" ) - with self.assertRaises(ValueError): - model.get_object_descendants( + with pytest.raises(ValueError): + model_index.get_object_descendants( type_name="IPVBaseCalculate", ids=None, domain="PV_Assets" ) - @mock.patch("requests.get", side_effect=mocked_requests) - def test_get_object_ancestors(self, mock_get): - model = ModelIndex(url=URL) - with mock.patch("requests.post", side_effect=mocked_requests): - result = model.get_object_ancestors( - type_name="IPVBaseCalculate", - ids=["Anything"], - domain="PV_Assets", - ) - assert result == ancestors + def test_get_object_ancestors(self, model_index): + result = model_index.get_object_ancestors( + type_name="IPVBaseCalculate", + ids=["Anything"], + domain="PV_Assets", + ) + assert result == ancestors - with self.assertRaises(ValueError): - model.get_object_ancestors( + with pytest.raises(ValueError): + model_index.get_object_ancestors( type_name=None, ids=["Anything"], domain="PV_Assets" ) - with self.assertRaises(ValueError): - model.get_object_ancestors( + with pytest.raises(ValueError): + model_index.get_object_ancestors( type_name="IPVBaseCalculate", ids=None, domain="PV_Assets" ) diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index cbd83ed..b224cab 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1164,15 +1164,6 @@ def test_get_write_historical_values_successful_with_error_codes( ][0]["StatusCode"]["Symbol"] ) - # @patch("requests.post") - # def test_write_historical_values_no_history_update_results(self, mock_request): - # converted_data = [ - # WriteHistoricalVariables(**item) - # for item in list_of_write_historical_values - # ] - # with pytest.raises(ValueError, match="No status codes returned, might indicate no values written"): - # self.opc.write_historical_values(converted_data) - class AsyncMockResponse: def __init__(self, json_data, status_code): From 8cd4e3fe09c246230a991857916903b3773f3710 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 10:34:40 +0200 Subject: [PATCH 24/35] Refactored ModelIndex tests and fixed assertions. --- tests/auth_client_test.py | 8 ++------ tests/conftest.py | 1 - tests/model_index_test.py | 34 ++++++++++++++-------------------- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 3d3abbb..5d40a79 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -384,9 +384,7 @@ def test_malformed_rest_url(self): with pytest.raises(ValidationError): AnyUrlModel(rest_url="invalid-url") - @patch( - "requests.get", side_effect=successful_self_service_mocked_requests - ) + @patch("requests.get", side_effect=successful_self_service_mocked_requests) def test_get_self_service_login_id_successful(self, mock_get, auth_client): auth_client.get_login_id() assert auth_client.id == auth_id @@ -406,9 +404,7 @@ def test_get_self_service_login_id_empty(self, mock_get, auth_client): with pytest.raises(RuntimeError): auth_client.get_login_id() - @patch( - "requests.get", side_effect=wrong_id_self_service_mocked_requests - ) + @patch("requests.get", side_effect=wrong_id_self_service_mocked_requests) def test_get_self_service_login_id_wrong_id(self, mock_get, auth_client): with pytest.raises(RuntimeError): auth_client.get_login_id() diff --git a/tests/conftest.py b/tests/conftest.py index 840d105..4db5018 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ from pyprediktormapclient.dwh.dwh import DWH from pyprediktormapclient.dwh.db import Db from pyprediktormapclient.auth_client import AUTH_CLIENT -from pyprediktormapclient.model_index import ModelIndex URL = "http://someserver.somedomain.com/v1/" username = "some@user.com" diff --git a/tests/model_index_test.py b/tests/model_index_test.py index 7cdc1f0..d1d56a2 100644 --- a/tests/model_index_test.py +++ b/tests/model_index_test.py @@ -79,8 +79,10 @@ def raise_for_status(self): # Check if the URL is malformed parsed_url = urlparse(args[0]) if not parsed_url.scheme: - raise requests.exceptions.MissingSchema("Invalid URL 'No_valid_url': No scheme supplied. Perhaps you meant http://No_valid_url?") - + raise requests.exceptions.MissingSchema( + "Invalid URL 'No_valid_url': No scheme supplied. Perhaps you meant http://No_valid_url?" + ) + if args[0] == f"{URL}query/object-types": return MockResponse(object_types, 200) elif args[0] == f"{URL}query/namespace-array": @@ -94,13 +96,16 @@ def raise_for_status(self): return MockResponse(None, 404) + requests.get = Mock(side_effect=mocked_requests) requests.post = Mock(side_effect=mocked_requests) + @pytest.fixture def model_index(): return ModelIndex(url=URL) + class AnyUrlModel(BaseModel): url: AnyUrl @@ -108,21 +113,21 @@ class AnyUrlModel(BaseModel): class TestCaseModelIndex: @patch("pyprediktormapclient.model_index.ModelIndex.get_object_types") - def test_init_variations(self, mock_get_object_types, model_index, mock_auth_client): + def setup_model_index_test_environment(self, mock_get_object_types): mock_get_object_types.return_value = object_types - # Without auth_client + def test_init_without_auth_client(self, model_index): assert "Authorization" not in model_index.headers assert "Cookie" not in model_index.headers - # With auth_client, no token, no session_token + def test_init_with_auth_client_no_tokens(self): auth_client = Mock(spec=[]) auth_client.token = None model = ModelIndex(url=URL, auth_client=auth_client) assert "Authorization" not in model.headers assert "Cookie" not in model.headers - # With auth_client, token, and session_token + def test_init_with_auth_client_both_tokens(self): auth_client = Mock() auth_client.token = Mock() auth_client.token.session_token = "test_token" @@ -133,19 +138,6 @@ def test_init_variations(self, mock_get_object_types, model_index, mock_auth_cli model.headers["Cookie"] == "ory_kratos_session=ory_session_token" ) - # With auth_client, no token, with session_token - auth_client.token = None - model = ModelIndex(url=URL, auth_client=auth_client) - assert "Authorization" not in model.headers - assert ( - model.headers["Cookie"] == "ory_kratos_session=ory_session_token" - ) - - # With session - session = requests.Session() - model = ModelIndex(url=URL, session=session) - assert model.session == session - def test_malformed_url(self): with pytest.raises(ValidationError): AnyUrlModel(url="not_an_url") @@ -203,7 +195,9 @@ def test_get_object_type_id_from_name(self, model_index): model_index.get_object_type_id_from_name("IPVBaseCalculate") == "6:0:1029" ) - assert model_index.get_object_type_id_from_name("NonExistentType") is None + assert ( + model_index.get_object_type_id_from_name("NonExistentType") is None + ) def test_get_objects_of_type(self, model_index): assert ( From fea128a51dde76ebd712995a52ac8b0ea699ce72 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 10:57:03 +0200 Subject: [PATCH 25/35] Adjusted test configurations to address test failures. --- tests/auth_client_test.py | 6 +++++- tests/dwh/db_test.py | 11 +++++++---- tests/dwh/dwh_test.py | 11 +++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 5d40a79..9468ffe 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -440,7 +440,11 @@ def test_get_self_service_login_token_successful( assert auth_client.token.session_token == auth_session_id expected_expires_at = Token.remove_nanoseconds(auth_expires_at) - assert auth_client.token.expires_at == expected_expires_at + assert auth_client.token.expires_at is not None + assert isinstance(auth_client.token.expires_at, datetime) + assert auth_client.token.expires_at.replace( + tzinfo=timezone.utc + ) == expected_expires_at.replace(tzinfo=timezone.utc) @patch( "requests.post", diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 6a32424..91849ae 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -145,11 +145,14 @@ def compare_signatures(impl_method, abstract_method): ), "execute method should return a list" def test_idwh_instantiation_raises_error(self): - with pytest.raises( - TypeError, - match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'", - ): + with pytest.raises(TypeError) as excinfo: IDWH() + error_message = str(excinfo.value) + assert "Can't instantiate abstract class IDWH" in error_message + assert "abstract methods" in error_message + assert "execute" in error_message + assert "fetch" in error_message + assert "version" in error_message @pytest.mark.parametrize("to_dataframe", [False, True]) def test_fetch_multiple_datasets( diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index 9e029b0..2a475dd 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -42,11 +42,14 @@ def compare_signatures(impl_method, abstract_method): assert dwh_instance.version.__annotations__["return"] == Dict def test_idwh_instantiation_raises_error(self): - with pytest.raises( - TypeError, - match="Can't instantiate abstract class IDWH without an implementation for abstract methods 'execute', 'fetch', 'version'", - ): + with pytest.raises(TypeError) as excinfo: IDWH() + error_message = str(excinfo.value) + assert "Can't instantiate abstract class IDWH" in error_message + assert "abstract methods" in error_message + assert "execute" in error_message + assert "fetch" in error_message + assert "version" in error_message @patch.object(DWH, "fetch") def test_version_with_results(self, mock_fetch, dwh_instance): From 6a8cad3fd42304063cdde7ba04d831274a2a7c0c Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 11:10:28 +0200 Subject: [PATCH 26/35] Resolved issues with test failures in the CI/CD pipeline. --- tests/auth_client_test.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 9468ffe..cf6f0b4 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -440,11 +440,15 @@ def test_get_self_service_login_token_successful( assert auth_client.token.session_token == auth_session_id expected_expires_at = Token.remove_nanoseconds(auth_expires_at) - assert auth_client.token.expires_at is not None - assert isinstance(auth_client.token.expires_at, datetime) + assert auth_client.token.expires_at is not None, "expires_at is None" + assert isinstance( + auth_client.token.expires_at, datetime + ), f"expires_at is not a datetime object, it's a {type(auth_client.token.expires_at)}" assert auth_client.token.expires_at.replace( tzinfo=timezone.utc - ) == expected_expires_at.replace(tzinfo=timezone.utc) + ) == expected_expires_at.replace( + tzinfo=timezone.utc + ), f"expires_at {auth_client.token.expires_at} does not match expected {expected_expires_at}" @patch( "requests.post", From b23ad3b63580ffa99b384fd9b61a7f451e0fd60c Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 11:46:27 +0200 Subject: [PATCH 27/35] Fixed test failure by updating token assertions and format. --- tests/auth_client_test.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index cf6f0b4..f780e60 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -436,19 +436,11 @@ def test_get_self_service_login_token_successful( auth_client.id = auth_id auth_client.get_login_token() - assert auth_client.token is not None - assert auth_client.token.session_token == auth_session_id - - expected_expires_at = Token.remove_nanoseconds(auth_expires_at) - assert auth_client.token.expires_at is not None, "expires_at is None" - assert isinstance( - auth_client.token.expires_at, datetime - ), f"expires_at is not a datetime object, it's a {type(auth_client.token.expires_at)}" - assert auth_client.token.expires_at.replace( - tzinfo=timezone.utc - ) == expected_expires_at.replace( - tzinfo=timezone.utc - ), f"expires_at {auth_client.token.expires_at} does not match expected {expected_expires_at}" + test_token = Token( + session_token=auth_session_id, expires_at=auth_expires_at + ) + assert auth_client.token.session_token == test_token.session_token + assert auth_client.token.expires_at == test_token.expires_at @patch( "requests.post", From c2ba3fcaed9538b3bc893934c81d86ca9c89b3c9 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 12:31:57 +0200 Subject: [PATCH 28/35] Ensured timezone-aware comparison for token expiration. --- requirements.txt | 1 + tests/auth_client_test.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d2d0d5b..2724d7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ ipykernel nest_asyncio pre-commit pytest-asyncio +python-dateutil python-dotenv sphinx_rtd_theme tenacity diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index f780e60..9a86faf 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -5,6 +5,8 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone import requests +from dateutil.parser import parse +from dateutil.tz import tzutc from pyprediktormapclient.auth_client import AUTH_CLIENT, Token @@ -436,11 +438,23 @@ def test_get_self_service_login_token_successful( auth_client.id = auth_id auth_client.get_login_token() + # Parse the expected datetime string + expected_expires_at = parse(auth_expires_at).replace(tzinfo=tzutc()) + test_token = Token( - session_token=auth_session_id, expires_at=auth_expires_at + session_token=auth_session_id, expires_at=expected_expires_at ) + assert auth_client.token.session_token == test_token.session_token - assert auth_client.token.expires_at == test_token.expires_at + + # Compare the datetime objects after ensuring they're both timezone-aware + actual_expires_at = auth_client.token.expires_at + if actual_expires_at.tzinfo is None: + actual_expires_at = actual_expires_at.replace(tzinfo=tzutc()) + + assert ( + actual_expires_at == expected_expires_at + ), f"Expected {expected_expires_at}, but got {actual_expires_at}" @patch( "requests.post", From 15e1ffef198df53e7d514c75ec41f7bf905da9f5 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 12:58:33 +0200 Subject: [PATCH 29/35] Added more detailed assertions with error messages. --- tests/auth_client_test.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 9a86faf..3a18083 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -438,7 +438,6 @@ def test_get_self_service_login_token_successful( auth_client.id = auth_id auth_client.get_login_token() - # Parse the expected datetime string expected_expires_at = parse(auth_expires_at).replace(tzinfo=tzutc()) test_token = Token( @@ -447,14 +446,16 @@ def test_get_self_service_login_token_successful( assert auth_client.token.session_token == test_token.session_token - # Compare the datetime objects after ensuring they're both timezone-aware - actual_expires_at = auth_client.token.expires_at - if actual_expires_at.tzinfo is None: - actual_expires_at = actual_expires_at.replace(tzinfo=tzutc()) + if auth_client.token.expires_at is None: + assert False, "Expected expires_at to be set, but it was None" + else: + actual_expires_at = auth_client.token.expires_at + if actual_expires_at.tzinfo is None: + actual_expires_at = actual_expires_at.replace(tzinfo=tzutc()) - assert ( - actual_expires_at == expected_expires_at - ), f"Expected {expected_expires_at}, but got {actual_expires_at}" + assert ( + actual_expires_at == expected_expires_at + ), f"Expected {expected_expires_at}, but got {actual_expires_at}" @patch( "requests.post", From 6b9188797751b1a14f18594dad02b5163aade8f9 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 13:35:41 +0200 Subject: [PATCH 30/35] Added debugging to auth client test. --- tests/auth_client_test.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index 3a18083..a07bcdd 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -438,24 +438,30 @@ def test_get_self_service_login_token_successful( auth_client.id = auth_id auth_client.get_login_token() + print(f"Auth client token: {auth_client.token}") + print(f"Auth client token expires_at: {auth_client.token.expires_at}") + expected_expires_at = parse(auth_expires_at).replace(tzinfo=tzutc()) + print(f"Expected expires_at: {expected_expires_at}") test_token = Token( session_token=auth_session_id, expires_at=expected_expires_at ) - assert auth_client.token.session_token == test_token.session_token + assert auth_client.token.session_token == test_token.session_token, \ + f"Expected session_token: {test_token.session_token}, got: {auth_client.token.session_token}" if auth_client.token.expires_at is None: + print("Mocked response content:") + print(successful_self_service_login_token_mocked_response) assert False, "Expected expires_at to be set, but it was None" else: actual_expires_at = auth_client.token.expires_at if actual_expires_at.tzinfo is None: actual_expires_at = actual_expires_at.replace(tzinfo=tzutc()) - - assert ( - actual_expires_at == expected_expires_at - ), f"Expected {expected_expires_at}, but got {actual_expires_at}" + + assert actual_expires_at == expected_expires_at, \ + f"Expected {expected_expires_at}, but got {actual_expires_at}" @patch( "requests.post", From d94f337429b464ae8867bf178bfecb490aa792e5 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 14:15:14 +0200 Subject: [PATCH 31/35] Addressed failing test error in CI/CD pipeline. --- notebooks/Example_Data_Downloading.ipynb | 12 +- notebooks/Exploring_API_Functions.ipynb | 10 +- ...ploring_API_Functions_Authentication.ipynb | 9377 +---------------- src/pyprediktormapclient/auth_client.py | 22 +- tests/auth_client_test.py | 27 +- 5 files changed, 52 insertions(+), 9396 deletions(-) diff --git a/notebooks/Example_Data_Downloading.ipynb b/notebooks/Example_Data_Downloading.ipynb index a913612..63a9178 100644 --- a/notebooks/Example_Data_Downloading.ipynb +++ b/notebooks/Example_Data_Downloading.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -128,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -158,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ diff --git a/notebooks/Exploring_API_Functions.ipynb b/notebooks/Exploring_API_Functions.ipynb index 156fa61..4cd3ca7 100644 --- a/notebooks/Exploring_API_Functions.ipynb +++ b/notebooks/Exploring_API_Functions.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -118,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ diff --git a/notebooks/Exploring_API_Functions_Authentication.ipynb b/notebooks/Exploring_API_Functions_Authentication.ipynb index dbbb89b..47105bd 100644 --- a/notebooks/Exploring_API_Functions_Authentication.ipynb +++ b/notebooks/Exploring_API_Functions_Authentication.ipynb @@ -52,20 +52,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Consider obtaining the envrionment variables from .env file if you are running this locally from source.\n", "dotenv_path = Path(\"../.env\")\n", @@ -116,27 +105,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'Idx': 0, 'Uri': 'http://opcfoundation.org/UA/'},\n", - " {'Idx': 1, 'Uri': 'http://prediktor.no/apis/ua/'},\n", - " {'Idx': 2, 'Uri': 'urn:prediktor:UIDEV-W2022-04:Scatec'},\n", - " {'Idx': 3, 'Uri': 'http://scatecsolar.com/EG-AS'},\n", - " {'Idx': 4, 'Uri': 'http://scatecsolar.com/Enterprise'},\n", - " {'Idx': 5, 'Uri': 'http://scatecsolar.com/JO-GL'},\n", - " {'Idx': 6, 'Uri': 'http://prediktor.no/PVTypes/'},\n", - " {'Idx': 7, 'Uri': 'http://powerview.com/enterprise'}]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Listed sites on the model index api server\n", "namespaces = model_data.get_namespace_array()\n", @@ -145,166 +116,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdNameBrowseNamePropsVars
06:0:1052ICalcTrackerICalcTracker[][]
16:0:1061EquipmentEventTypeEquipmentEventType[][]
26:0:1128EnergyAndPowerMeterEventTypeEnergyAndPowerMeterEventType[][]
36:0:1263EnergyAndPowerMeterCommLossEventTypeEnergyAndPowerMeterCommLossEventType[][]
46:0:1266EnergyAndPowerMeterErrorEventTypeEnergyAndPowerMeterErrorEventType[][]
..................
2396:0:1050OpcUaHiveModuleOpcUaHiveModule[][]
2406:0:1057BaseHiveModuleNoItemsTypeBaseHiveModuleNoItemsType[][]
2416:0:1067LoggerHiveModuleLoggerHiveModule[][]
2426:0:1055EquipmentTemplateDefinitionTypeEquipmentTemplateDefinitionType[][]
2436:0:1066HivePropertyTypeHivePropertyType[][]
\n", - "

244 rows × 5 columns

\n", - "
" - ], - "text/plain": [ - " Id Name \\\n", - "0 6:0:1052 ICalcTracker \n", - "1 6:0:1061 EquipmentEventType \n", - "2 6:0:1128 EnergyAndPowerMeterEventType \n", - "3 6:0:1263 EnergyAndPowerMeterCommLossEventType \n", - "4 6:0:1266 EnergyAndPowerMeterErrorEventType \n", - ".. ... ... \n", - "239 6:0:1050 OpcUaHiveModule \n", - "240 6:0:1057 BaseHiveModuleNoItemsType \n", - "241 6:0:1067 LoggerHiveModule \n", - "242 6:0:1055 EquipmentTemplateDefinitionType \n", - "243 6:0:1066 HivePropertyType \n", - "\n", - " BrowseName Props Vars \n", - "0 ICalcTracker [] [] \n", - "1 EquipmentEventType [] [] \n", - "2 EnergyAndPowerMeterEventType [] [] \n", - "3 EnergyAndPowerMeterCommLossEventType [] [] \n", - "4 EnergyAndPowerMeterErrorEventType [] [] \n", - ".. ... ... ... \n", - "239 OpcUaHiveModule [] [] \n", - "240 BaseHiveModuleNoItemsType [] [] \n", - "241 LoggerHiveModule [] [] \n", - "242 EquipmentTemplateDefinitionType [] [] \n", - "243 HivePropertyType [] [] \n", - "\n", - "[244 rows x 5 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Types of Objects\n", "object_types_json = model_data.get_object_types()\n", @@ -314,117 +128,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdName
06:0:1052ICalcTracker
16:0:1061EquipmentEventType
26:0:1128EnergyAndPowerMeterEventType
36:0:1263EnergyAndPowerMeterCommLossEventType
46:0:1266EnergyAndPowerMeterErrorEventType
.........
1186:0:1050OpcUaHiveModule
1196:0:1057BaseHiveModuleNoItemsType
1206:0:1067LoggerHiveModule
1216:0:1055EquipmentTemplateDefinitionType
1226:0:1066HivePropertyType
\n", - "

122 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " Id Name\n", - "0 6:0:1052 ICalcTracker\n", - "1 6:0:1061 EquipmentEventType\n", - "2 6:0:1128 EnergyAndPowerMeterEventType\n", - "3 6:0:1263 EnergyAndPowerMeterCommLossEventType\n", - "4 6:0:1266 EnergyAndPowerMeterErrorEventType\n", - ".. ... ...\n", - "118 6:0:1050 OpcUaHiveModule\n", - "119 6:0:1057 BaseHiveModuleNoItemsType\n", - "120 6:0:1067 LoggerHiveModule\n", - "121 6:0:1055 EquipmentTemplateDefinitionType\n", - "122 6:0:1066 HivePropertyType\n", - "\n", - "[122 rows x 2 columns]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Unique types of Objects\n", "object_types_unique = object_types.dataframe[[\"Id\", \"Name\"]].drop_duplicates()\n", @@ -433,20 +139,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'6:0:1009'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# To get typeId by type name of an object\n", "object_type_id = model_data.get_object_type_id_from_name(\"SiteType\")\n", @@ -455,20 +150,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['EG-AS', 'JO-GL']" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# To get the objects of a type\n", "sites_json = model_data.get_objects_of_type(\"SiteType\")\n", @@ -480,204 +164,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdTypeNameVariableIdVariableNameVariableIdSplit
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Alarms.CommLossPlantDeviceCommLossPlantDevice{'Id': 'Enterprise.EG-AS.Alarms.CommLossPlantD...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Signals.PPC.IsCurtailmentPPC.IsCurtailment{'Id': 'Enterprise.EG-AS.Signals.PPC.IsCurtail...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Signals.State.IsDayState.IsDay{'Id': 'Enterprise.EG-AS.Signals.State.IsDay',...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Parameters.ContractDurationContractDuration{'Id': 'Enterprise.EG-AS.Parameters.ContractDu...
03:1:Enterprise.EG-AS6:0:1009EG-AS3:1:Enterprise.EG-AS.Parameters.RegionKeyRegionKey{'Id': 'Enterprise.EG-AS.Parameters.RegionKey'...
.....................
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationDiffuseHorizontal{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationHorizontal{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.Weather.Irradiati...Weather.IrradiationInCline{'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.ACActiveEnergy.TotalACActiveEnergy.Total{'Id': 'Enterprise.JO-GL.Signals.ACActiveEnerg...
15:1:Enterprise.JO-GL6:0:1009JO-GL5:1:Enterprise.JO-GL.Signals.StatusStatus{'Id': 'Enterprise.JO-GL.Signals.Status', 'Nam...
\n", - "

232 rows × 6 columns

\n", - "
" - ], - "text/plain": [ - " Id Type Name \\\n", - "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", - "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", - "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", - "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", - "0 3:1:Enterprise.EG-AS 6:0:1009 EG-AS \n", - ".. ... ... ... \n", - "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", - "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", - "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", - "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", - "1 5:1:Enterprise.JO-GL 6:0:1009 JO-GL \n", - "\n", - " VariableId \\\n", - "0 3:1:Enterprise.EG-AS.Alarms.CommLossPlantDevice \n", - "0 3:1:Enterprise.EG-AS.Signals.PPC.IsCurtailment \n", - "0 3:1:Enterprise.EG-AS.Signals.State.IsDay \n", - "0 3:1:Enterprise.EG-AS.Parameters.ContractDuration \n", - "0 3:1:Enterprise.EG-AS.Parameters.RegionKey \n", - ".. ... \n", - "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", - "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", - "1 5:1:Enterprise.JO-GL.Signals.Weather.Irradiati... \n", - "1 5:1:Enterprise.JO-GL.Signals.ACActiveEnergy.Total \n", - "1 5:1:Enterprise.JO-GL.Signals.Status \n", - "\n", - " VariableName \\\n", - "0 CommLossPlantDevice \n", - "0 PPC.IsCurtailment \n", - "0 State.IsDay \n", - "0 ContractDuration \n", - "0 RegionKey \n", - ".. ... \n", - "1 Weather.IrradiationDiffuseHorizontal \n", - "1 Weather.IrradiationHorizontal \n", - "1 Weather.IrradiationInCline \n", - "1 ACActiveEnergy.Total \n", - "1 Status \n", - "\n", - " VariableIdSplit \n", - "0 {'Id': 'Enterprise.EG-AS.Alarms.CommLossPlantD... \n", - "0 {'Id': 'Enterprise.EG-AS.Signals.PPC.IsCurtail... \n", - "0 {'Id': 'Enterprise.EG-AS.Signals.State.IsDay',... \n", - "0 {'Id': 'Enterprise.EG-AS.Parameters.ContractDu... \n", - "0 {'Id': 'Enterprise.EG-AS.Parameters.RegionKey'... \n", - ".. ... \n", - "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", - "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", - "1 {'Id': 'Enterprise.JO-GL.Signals.Weather.Irrad... \n", - "1 {'Id': 'Enterprise.JO-GL.Signals.ACActiveEnerg... \n", - "1 {'Id': 'Enterprise.JO-GL.Signals.Status', 'Nam... \n", - "\n", - "[232 rows x 6 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Analytics helper\n", "sites.variables_as_dataframe()" @@ -685,40 +174,18 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['3:1:Enterprise.EG-AS', '5:1:Enterprise.JO-GL']" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "sites.list_of_ids()" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'3:1:Enterprise.EG-AS'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Selecting the single site\n", "site_id = sites.list_of_ids()[0]\n", @@ -727,179 +194,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdNameTypePropsVars
03:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01EG-AS-TS01-I01-SM01-CH01StringSetType[{'DisplayName': 'ChannelNo', 'Value': '01'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
13:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH02EG-AS-TS01-I01-SM01-CH02StringSetType[{'DisplayName': 'ChannelNo', 'Value': '02'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
23:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH03EG-AS-TS01-I01-SM01-CH03StringSetType[{'DisplayName': 'ChannelNo', 'Value': '03'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
33:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH04EG-AS-TS01-I01-SM01-CH04StringSetType[{'DisplayName': 'ChannelNo', 'Value': '04'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
43:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH05EG-AS-TS01-I01-SM01-CH05StringSetType[{'DisplayName': 'ChannelNo', 'Value': '05'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
..................
29333:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH05EG-AS-TS11-I22-SM13-CH05StringSetType[{'DisplayName': 'ChannelNo', 'Value': '05'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29343:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH06EG-AS-TS11-I22-SM13-CH06StringSetType[{'DisplayName': 'ChannelNo', 'Value': '06'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29353:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH07EG-AS-TS11-I22-SM13-CH07StringSetType[{'DisplayName': 'ChannelNo', 'Value': '07'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29363:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH08EG-AS-TS11-I22-SM13-CH08StringSetType[{'DisplayName': 'ChannelNo', 'Value': '08'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
29373:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09EG-AS-TS11-I22-SM13-CH09StringSetType[{'DisplayName': 'ChannelNo', 'Value': '09'}, ...[{'DisplayName': 'StringDisconnected', 'Id': '...
\n", - "

2938 rows × 5 columns

\n", - "
" - ], - "text/plain": [ - " Id Name \\\n", - "0 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01 EG-AS-TS01-I01-SM01-CH01 \n", - "1 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH02 EG-AS-TS01-I01-SM01-CH02 \n", - "2 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH03 EG-AS-TS01-I01-SM01-CH03 \n", - "3 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH04 EG-AS-TS01-I01-SM01-CH04 \n", - "4 3:1:Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH05 EG-AS-TS01-I01-SM01-CH05 \n", - "... ... ... \n", - "2933 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH05 EG-AS-TS11-I22-SM13-CH05 \n", - "2934 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH06 EG-AS-TS11-I22-SM13-CH06 \n", - "2935 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH07 EG-AS-TS11-I22-SM13-CH07 \n", - "2936 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH08 EG-AS-TS11-I22-SM13-CH08 \n", - "2937 3:1:Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09 EG-AS-TS11-I22-SM13-CH09 \n", - "\n", - " Type Props \\\n", - "0 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '01'}, ... \n", - "1 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '02'}, ... \n", - "2 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '03'}, ... \n", - "3 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '04'}, ... \n", - "4 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '05'}, ... \n", - "... ... ... \n", - "2933 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '05'}, ... \n", - "2934 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '06'}, ... \n", - "2935 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '07'}, ... \n", - "2936 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '08'}, ... \n", - "2937 StringSetType [{'DisplayName': 'ChannelNo', 'Value': '09'}, ... \n", - "\n", - " Vars \n", - "0 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "1 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "2 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "3 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "4 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "... ... \n", - "2933 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "2934 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "2935 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "2936 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "2937 [{'DisplayName': 'StringDisconnected', 'Id': '... \n", - "\n", - "[2938 rows x 5 columns]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Get all stringsets for one park\n", "string_sets_for_first_park_as_json = model_data.get_object_descendants(\n", @@ -924,204 +221,9 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdNameTypeVariableIdVariableNameVariableIdSplit
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....TrackerOutOfPos{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasHighSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasMediumSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....State.HasLowSeverityAlarm{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
03:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001EG-AS-TR-TB01.TR001TrackerType3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001....CommLoss{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR...
.....................
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....TrackingLimitWestAngle{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....MotorPressure{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....Category{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....StateCode{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
58713:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178EG-AS-TR-TB11.TR178TrackerType3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178....Status{'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR...
\n", - "

111568 rows × 6 columns

\n", - "
" - ], - "text/plain": [ - " Id Name \\\n", - "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", - "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", - "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", - "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", - "0 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001 EG-AS-TR-TB01.TR001 \n", - "... ... ... \n", - "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", - "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", - "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", - "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", - "5871 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178 EG-AS-TR-TB11.TR178 \n", - "\n", - " Type VariableId \\\n", - "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", - "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", - "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", - "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", - "0 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.... \n", - "... ... ... \n", - "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", - "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", - "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", - "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", - "5871 TrackerType 3:1:Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR178.... \n", - "\n", - " VariableName \\\n", - "0 TrackerOutOfPos \n", - "0 State.HasHighSeverityAlarm \n", - "0 State.HasMediumSeverityAlarm \n", - "0 State.HasLowSeverityAlarm \n", - "0 CommLoss \n", - "... ... \n", - "5871 TrackingLimitWestAngle \n", - "5871 MotorPressure \n", - "5871 Category \n", - "5871 StateCode \n", - "5871 Status \n", - "\n", - " VariableIdSplit \n", - "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", - "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", - "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", - "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", - "0 {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR... \n", - "... ... \n", - "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", - "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", - "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", - "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", - "5871 {'Id': 'Enterprise.EG-AS.S1.MMSTB11.TR-TB11.TR... \n", - "\n", - "[111568 rows x 6 columns]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Ancestors of an object type, get all trackers that are ancestor of the parks string sets\n", "\n", @@ -1153,8020 +255,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[{'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR002.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334401Z',\n", - " 'Value': 20.00982,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR004.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7284264Z',\n", - " 'Value': 39.937416,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR003.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334346Z',\n", - " 'Value': 39.95844,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR005.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7114424Z',\n", - " 'Value': 40.297165,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR006.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334283Z',\n", - " 'Value': 39.893333,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR008.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7184438Z',\n", - " 'Value': 38.764885,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR007.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7265985Z',\n", - " 'Value': 39.426506,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR009.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154025Z',\n", - " 'Value': 38.417027,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR010.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334392Z',\n", - " 'Value': 39.488316,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR011.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7284229Z',\n", - " 'Value': 39.20795,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR012.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6950084Z',\n", - " 'Value': 39.74066,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR014.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004301Z',\n", - " 'Value': 39.336094,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR013.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334246Z',\n", - " 'Value': 39.869442,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR015.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733442Z',\n", - " 'Value': 38.648,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR016.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334327Z',\n", - " 'Value': 38.795746,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR018.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334378Z',\n", - " 'Value': 40.091286,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR017.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334425Z',\n", - " 'Value': 40.164062,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR019.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334429Z',\n", - " 'Value': 38.7463,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR020.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254122Z',\n", - " 'Value': 38.563805,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR021.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334431Z',\n", - " 'Value': 40.117725,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR022.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334432Z',\n", - " 'Value': 40.0371,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR024.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014236Z',\n", - " 'Value': 40.117725,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR023.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334233Z',\n", - " 'Value': 40.02778,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR025.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004288Z',\n", - " 'Value': 39.271294,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR026.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7284452Z',\n", - " 'Value': 40.09894,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR028.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314134Z',\n", - " 'Value': 40.02778,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR027.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334435Z',\n", - " 'Value': 39.348305,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR029.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974027Z',\n", - " 'Value': 38.480316,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR030.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314389Z',\n", - " 'Value': 39.18548,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR031.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334182Z',\n", - " 'Value': 39.961735,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR032.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7266032Z',\n", - " 'Value': 39.271294,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR034.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314072Z',\n", - " 'Value': 39.968727,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR033.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334437Z',\n", - " 'Value': 38.628315,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR035.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334439Z',\n", - " 'Value': 38.821533,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR036.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334441Z',\n", - " 'Value': 39.348305,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR038.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334443Z',\n", - " 'Value': 39.448994,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR037.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7034534Z',\n", - " 'Value': 38.359806,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR039.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334444Z',\n", - " 'Value': 38.813255,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR040.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334304Z',\n", - " 'Value': 38.480316,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR071.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294087Z',\n", - " 'Value': 39.088306,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR072.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314439Z',\n", - " 'Value': 39.82448,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR074.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214305Z',\n", - " 'Value': 40.183846,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR073.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334475Z',\n", - " 'Value': 39.10047,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR075.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014177Z',\n", - " 'Value': 39.293728,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR076.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974381Z',\n", - " 'Value': 38.941372,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR078.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124285Z',\n", - " 'Value': 38.92468,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR077.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334416Z',\n", - " 'Value': 39.59775,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR079.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334482Z',\n", - " 'Value': 39.369774,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR080.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7174464Z',\n", - " 'Value': 39.944653,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR061.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7104211Z',\n", - " 'Value': 39.68624,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR062.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994225Z',\n", - " 'Value': 39.448288,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR064.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334469Z',\n", - " 'Value': 39.76726,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR063.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334464Z',\n", - " 'Value': 39.7911,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR065.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963584Z',\n", - " 'Value': 38.4654,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR066.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910524Z',\n", - " 'Value': 38.101276,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR068.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334472Z',\n", - " 'Value': 39.444527,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR067.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920386Z',\n", - " 'Value': 39.58335,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR069.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334474Z',\n", - " 'Value': 38.274776,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR070.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7074495Z',\n", - " 'Value': 38.525463,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR051.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334454Z',\n", - " 'Value': 39.135864,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR052.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334455Z',\n", - " 'Value': 39.74165,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR054.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910495Z',\n", - " 'Value': 38.86965,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR053.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154267Z',\n", - " 'Value': 39.895676,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR055.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324273Z',\n", - " 'Value': 39.636486,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR056.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7304351Z',\n", - " 'Value': 38.616066,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR058.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733446Z',\n", - " 'Value': 39.966393,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR057.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324436Z',\n", - " 'Value': 39.798927,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR059.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.707413Z',\n", - " 'Value': 39.86294,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR060.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214557Z',\n", - " 'Value': 38.576267,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR041.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7164483Z',\n", - " 'Value': 39.77297,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR042.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244401Z',\n", - " 'Value': 38.479317,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR044.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7194263Z',\n", - " 'Value': 39.138203,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR043.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7234174Z',\n", - " 'Value': 39.410217,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR045.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334448Z',\n", - " 'Value': 38.256565,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR046.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974214Z',\n", - " 'Value': 39.57831,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR048.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334373Z',\n", - " 'Value': 38.546345,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR047.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054104Z',\n", - " 'Value': 40.17925,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR049.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7174425Z',\n", - " 'Value': 40.15407,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR050.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244407Z',\n", - " 'Value': 39.477345,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR081.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334316Z',\n", - " 'Value': 38.30171,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR082.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254206Z',\n", - " 'Value': 39.335415,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR084.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334484Z',\n", - " 'Value': 39.482883,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR083.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.694009Z',\n", - " 'Value': 39.734528,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR085.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334485Z',\n", - " 'Value': 38.66229,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR086.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.707424Z',\n", - " 'Value': 40.226,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR088.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334489Z',\n", - " 'Value': 38.165596,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR087.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7104158Z',\n", - " 'Value': 39.07549,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR089.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733449Z',\n", - " 'Value': 39.488422,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR090.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.720428Z',\n", - " 'Value': 38.802856,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR091.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.695007Z',\n", - " 'Value': 38.416016,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR092.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334492Z',\n", - " 'Value': 39.11174,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR094.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334166Z',\n", - " 'Value': 39.698303,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR093.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334397Z',\n", - " 'Value': 39.56181,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR095.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334494Z',\n", - " 'Value': 39.41977,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR096.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334496Z',\n", - " 'Value': 39.00415,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR098.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054515Z',\n", - " 'Value': 38.210358,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR097.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334498Z',\n", - " 'Value': 39.15221,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR099.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214108Z',\n", - " 'Value': 40.00306,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR100.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334253Z',\n", - " 'Value': 38.696022,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR101.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334501Z',\n", - " 'Value': 38.7922,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR102.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334468Z',\n", - " 'Value': 38.982304,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR104.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334504Z',\n", - " 'Value': 39.395348,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR103.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214084Z',\n", - " 'Value': 38.24809,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR105.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334505Z',\n", - " 'Value': 38.646324,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR106.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124062Z',\n", - " 'Value': 39.640224,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR108.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334508Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR107.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7184488Z',\n", - " 'Value': 39.834618,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR109.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6964056Z',\n", - " 'Value': 38.23865,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR110.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6983988Z',\n", - " 'Value': 38.86368,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR111.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054354Z',\n", - " 'Value': 39.45928,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR112.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334511Z',\n", - " 'Value': 39.847073,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR114.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7194315Z',\n", - " 'Value': 38.547565,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR113.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7063999Z',\n", - " 'Value': 39.246708,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR115.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7271216Z',\n", - " 'Value': 38.964947,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR116.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7234039Z',\n", - " 'Value': 39.932182,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR118.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974451Z',\n", - " 'Value': 40.094357,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR117.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334097Z',\n", - " 'Value': 39.842464,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR119.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910345Z',\n", - " 'Value': 39.845444,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR120.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334515Z',\n", - " 'Value': 39.96943,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR131.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334512Z',\n", - " 'Value': 39.403713,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR132.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334527Z',\n", - " 'Value': 40.1329,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR133.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974285Z',\n", - " 'Value': 38.44644,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR134.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334421Z',\n", - " 'Value': 39.736664,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR135.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7024352Z',\n", - " 'Value': 39.254227,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR136.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7094278Z',\n", - " 'Value': 38.39941,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR137.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334276Z',\n", - " 'Value': 38.8705,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR138.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334231Z',\n", - " 'Value': 39.11725,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR139.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334122Z',\n", - " 'Value': 39.085377,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR140.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334532Z',\n", - " 'Value': 38.234764,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR121.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6960057Z',\n", - " 'Value': 39.273518,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR122.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910312Z',\n", - " 'Value': 39.378483,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR123.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733452Z',\n", - " 'Value': 38.45839,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR124.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334446Z',\n", - " 'Value': 38.27804,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR125.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334521Z',\n", - " 'Value': 40.069504,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR126.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334288Z',\n", - " 'Value': 39.804817,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR127.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334522Z',\n", - " 'Value': 38.57075,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR128.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334523Z',\n", - " 'Value': 38.319588,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR129.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.73343Z',\n", - " 'Value': 39.81896,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR130.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334526Z',\n", - " 'Value': 39.566437,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR242.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940364Z',\n", - " 'Value': 39.44667,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR241.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054231Z',\n", - " 'Value': 40.021214,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR243.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344058Z',\n", - " 'Value': 38.65197,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR244.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.721432Z',\n", - " 'Value': 39.945236,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR246.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7164425Z',\n", - " 'Value': 39.52573,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR245.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7224288Z',\n", - " 'Value': 39.7197,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR247.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344062Z',\n", - " 'Value': 39.13543,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR248.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344066Z',\n", - " 'Value': 38.94027,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR250.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734407Z',\n", - " 'Value': 39.773697,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR249.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344068Z',\n", - " 'Value': 39.559357,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR252.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7034049Z',\n", - " 'Value': 39.39085,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR251.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344074Z',\n", - " 'Value': 39.544086,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR253.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.731408Z',\n", - " 'Value': 39.41263,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR254.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344079Z',\n", - " 'Value': 38.13906,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR256.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734408Z',\n", - " 'Value': 38.6691,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR255.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.71841Z',\n", - " 'Value': 40.068417,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR257.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294025Z',\n", - " 'Value': 38.549046,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR258.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344089Z',\n", - " 'Value': 39.916183,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR260.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344093Z',\n", - " 'Value': 38.91388,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR259.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014037Z',\n", - " 'Value': 39.523148,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR262.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344096Z',\n", - " 'Value': 38.717606,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR261.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7194121Z',\n", - " 'Value': 39.89171,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR263.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014241Z',\n", - " 'Value': 38.91663,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR264.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920102Z',\n", - " 'Value': 39.07026,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR266.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344103Z',\n", - " 'Value': 40.11856,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR265.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6950211Z',\n", - " 'Value': 39.17636,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR267.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004098Z',\n", - " 'Value': 39.230713,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR268.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344105Z',\n", - " 'Value': 38.88261,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR270.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344107Z',\n", - " 'Value': 39.035175,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR269.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910499Z',\n", - " 'Value': 39.870422,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR272.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7174237Z',\n", - " 'Value': 39.945263,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR271.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154412Z',\n", - " 'Value': 39.57766,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR273.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.725426Z',\n", - " 'Value': 40.050755,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR274.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734411Z',\n", - " 'Value': 39.593277,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR276.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994301Z',\n", - " 'Value': 38.47257,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR275.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314065Z',\n", - " 'Value': 39.58106,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR277.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214234Z',\n", - " 'Value': 40.277138,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR278.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294077Z',\n", - " 'Value': 40.155914,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR280.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344118Z',\n", - " 'Value': 38.441658,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR279.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344116Z',\n", - " 'Value': 38.672085,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR232.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344031Z',\n", - " 'Value': 38.39525,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR231.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940069Z',\n", - " 'Value': 39.895023,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR233.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344032Z',\n", - " 'Value': 38.925884,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR234.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324109Z',\n", - " 'Value': 38.453194,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR236.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344045Z',\n", - " 'Value': 39.69611,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR235.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344038Z',\n", - " 'Value': 38.970055,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR237.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344048Z',\n", - " 'Value': 38.83117,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR238.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734405Z',\n", - " 'Value': 39.499306,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR240.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344054Z',\n", - " 'Value': 39.711494,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR239.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273885Z',\n", - " 'Value': 39.04134,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR222.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134056Z',\n", - " 'Value': 39.314827,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR221.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334333Z',\n", - " 'Value': 38.66296,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR223.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733447Z',\n", - " 'Value': 39.55961,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR224.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7044362Z',\n", - " 'Value': 39.352505,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR226.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334458Z',\n", - " 'Value': 39.022774,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR225.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334463Z',\n", - " 'Value': 39.615925,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR227.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334461Z',\n", - " 'Value': 39.504097,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR228.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734403Z',\n", - " 'Value': 40.013596,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR230.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930048Z',\n", - " 'Value': 38.451878,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR229.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334438Z',\n", - " 'Value': 39.184235,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR212.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7064038Z',\n", - " 'Value': 39.886215,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR211.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134145Z',\n", - " 'Value': 40.061974,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR213.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214083Z',\n", - " 'Value': 39.39644,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR214.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334326Z',\n", - " 'Value': 38.56171,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR216.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334417Z',\n", - " 'Value': 39.039494,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR215.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324421Z',\n", - " 'Value': 38.67933,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR217.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334419Z',\n", - " 'Value': 39.831093,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR218.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7224459Z',\n", - " 'Value': 38.3613,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR220.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7044497Z',\n", - " 'Value': 39.083828,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR219.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920155Z',\n", - " 'Value': 38.58721,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR202.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344008Z',\n", - " 'Value': 39.726665,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR201.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254466Z',\n", - " 'Value': 39.913795,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR203.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324456Z',\n", - " 'Value': 38.191315,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR204.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334282Z',\n", - " 'Value': 39.184402,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR206.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334403Z',\n", - " 'Value': 38.127216,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR205.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334321Z',\n", - " 'Value': 39.79701,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR207.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7266076Z',\n", - " 'Value': 40.08256,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR208.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344027Z',\n", - " 'Value': 38.74573,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR210.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334414Z',\n", - " 'Value': 39.827778,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR209.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344029Z',\n", - " 'Value': 39.591526,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR162.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334548Z',\n", - " 'Value': 39.351402,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR161.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334546Z',\n", - " 'Value': 39.36829,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR163.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314201Z',\n", - " 'Value': 38.836494,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR164.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334184Z',\n", - " 'Value': 1.5909697,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR166.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7184313Z',\n", - " 'Value': 39.76395,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR165.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733455Z',\n", - " 'Value': 39.817993,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR167.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7094313Z',\n", - " 'Value': 39.418304,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR168.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134379Z',\n", - " 'Value': 39.135853,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR170.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6964047Z',\n", - " 'Value': 38.71799,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR169.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334249Z',\n", - " 'Value': 39.09251,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR172.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334514Z',\n", - " 'Value': 38.719414,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR171.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734401Z',\n", - " 'Value': 39.547386,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR173.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930072Z',\n", - " 'Value': 38.697216,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR174.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930058Z',\n", - " 'Value': 39.425728,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR176.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334524Z',\n", - " 'Value': 39.991196,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR175.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334528Z',\n", - " 'Value': 39.17914,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR177.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344014Z',\n", - " 'Value': 38.37678,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR178.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930256Z',\n", - " 'Value': 39.94758,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR180.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7114229Z',\n", - " 'Value': 39.66571,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR179.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334114Z',\n", - " 'Value': 39.904457,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR182.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7164385Z',\n", - " 'Value': 39.915752,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR181.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733432Z',\n", - " 'Value': 39.03744,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR183.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7094193Z',\n", - " 'Value': 39.73549,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR184.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7144389Z',\n", - " 'Value': 38.246742,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR186.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733435Z',\n", - " 'Value': 39.452206,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR185.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344018Z',\n", - " 'Value': 38.31337,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR187.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054002Z',\n", - " 'Value': 38.719414,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR188.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334194Z',\n", - " 'Value': 40.19374,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR190.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254267Z',\n", - " 'Value': 40.089172,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR189.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334301Z',\n", - " 'Value': 38.27148,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR192.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994457Z',\n", - " 'Value': 38.218952,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR191.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344021Z',\n", - " 'Value': 38.234486,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR193.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733424Z',\n", - " 'Value': 39.944366,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR194.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.733436Z',\n", - " 'Value': 39.093487,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR196.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.73345Z',\n", - " 'Value': 38.188835,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR195.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314405Z',\n", - " 'Value': 40.10935,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR197.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.715433Z',\n", - " 'Value': 38.770123,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR198.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.704425Z',\n", - " 'Value': 38.546158,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR200.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7174238Z',\n", - " 'Value': 39.48254,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR199.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7083995Z',\n", - " 'Value': 39.31738,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR151.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334537Z',\n", - " 'Value': 38.207817,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR152.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334538Z',\n", - " 'Value': 39.943874,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR153.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334203Z',\n", - " 'Value': 39.43975,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR154.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7324353Z',\n", - " 'Value': 39.81792,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR155.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334539Z',\n", - " 'Value': 39.971394,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR156.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334117Z',\n", - " 'Value': 38.746563,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR157.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334385Z',\n", - " 'Value': 38.872536,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR158.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334286Z',\n", - " 'Value': 39.908062,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR159.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334404Z',\n", - " 'Value': 39.463634,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR160.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334284Z',\n", - " 'Value': 38.842228,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR141.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004184Z',\n", - " 'Value': 40.222397,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR142.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054172Z',\n", - " 'Value': 40.08209,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR143.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334534Z',\n", - " 'Value': 38.200974,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR144.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7194385Z',\n", - " 'Value': -45.325176,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR145.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.692042Z',\n", - " 'Value': 38.298748,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR146.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910275Z',\n", - " 'Value': 39.455357,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR147.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6939999Z',\n", - " 'Value': 39.66239,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR148.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7044486Z',\n", - " 'Value': 39.985497,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR149.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334352Z',\n", - " 'Value': 39.775578,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR150.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.725401Z',\n", - " 'Value': 39.349552,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994258Z',\n", - " 'Value': 38.619766,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR002.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344123Z',\n", - " 'Value': 39.452244,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR004.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7064394Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR003.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930134Z',\n", - " 'Value': 38.533504,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR005.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004372Z',\n", - " 'Value': 39.131474,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR006.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344135Z',\n", - " 'Value': 39.29539,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR008.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734414Z',\n", - " 'Value': 39.82251,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR007.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344138Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR009.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.713419Z',\n", - " 'Value': 39.620743,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR010.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7334227Z',\n", - " 'Value': 39.736233,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR011.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940095Z',\n", - " 'Value': 39.127758,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR012.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154359Z',\n", - " 'Value': 38.72058,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR014.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7084069Z',\n", - " 'Value': 38.4853,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR013.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344152Z',\n", - " 'Value': 38.62004,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR015.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154365Z',\n", - " 'Value': 38.32721,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR016.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974335Z',\n", - " 'Value': 38.61559,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR018.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344161Z',\n", - " 'Value': 38.171993,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR017.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920385Z',\n", - " 'Value': 38.44734,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR019.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7266203Z',\n", - " 'Value': 39.895794,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR020.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344165Z',\n", - " 'Value': 38.194336,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR021.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344167Z',\n", - " 'Value': 39.926956,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR022.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344067Z',\n", - " 'Value': 38.314487,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR024.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7194497Z',\n", - " 'Value': 39.926956,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR023.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7144503Z',\n", - " 'Value': 40.20513,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR025.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124173Z',\n", - " 'Value': 39.44503,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR026.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344173Z',\n", - " 'Value': 40.160927,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR028.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344176Z',\n", - " 'Value': 40.20513,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR027.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344175Z',\n", - " 'Value': 39.586636,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR029.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994124Z',\n", - " 'Value': 39.20667,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR030.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254492Z',\n", - " 'Value': 38.824375,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR031.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7084363Z',\n", - " 'Value': 40.023903,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR032.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344179Z',\n", - " 'Value': 39.44503,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR034.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004476Z',\n", - " 'Value': 38.573963,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR033.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014445Z',\n", - " 'Value': 38.834164,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR035.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344039Z',\n", - " 'Value': 39.343937,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR036.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940345Z',\n", - " 'Value': 39.586636,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR038.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134387Z',\n", - " 'Value': 38.200493,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR037.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7074343Z',\n", - " 'Value': 39.889774,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR039.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7234116Z',\n", - " 'Value': 39.403175,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR040.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.695015Z',\n", - " 'Value': 39.20667,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR071.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344262Z',\n", - " 'Value': 38.466988,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR072.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344073Z',\n", - " 'Value': 38.3039,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR074.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344269Z',\n", - " 'Value': 39.958927,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR073.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344266Z',\n", - " 'Value': 38.537994,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR075.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7074089Z',\n", - " 'Value': 39.86813,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR076.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344272Z',\n", - " 'Value': 38.460796,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR078.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344282Z',\n", - " 'Value': 39.002003,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR077.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7034061Z',\n", - " 'Value': -36.51047,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR079.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.705411Z',\n", - " 'Value': 38.37783,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR080.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6950392Z',\n", - " 'Value': 39.860077,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR061.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004229Z',\n", - " 'Value': 38.188156,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR062.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930322Z',\n", - " 'Value': 39.5255,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR064.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920054Z',\n", - " 'Value': 39.638214,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR063.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6961584Z',\n", - " 'Value': 38.321075,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR065.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273664Z',\n", - " 'Value': 38.181065,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR066.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344248Z',\n", - " 'Value': 39.537987,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR068.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910283Z',\n", - " 'Value': 38.888752,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR067.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7266143Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR069.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6973978Z',\n", - " 'Value': 38.537754,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR070.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.727366Z',\n", - " 'Value': 39.255383,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR051.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7084125Z',\n", - " 'Value': 40.22105,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR052.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7264027Z',\n", - " 'Value': 38.229202,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR054.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344222Z',\n", - " 'Value': 38.11777,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR053.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963838Z',\n", - " 'Value': 39.153023,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR055.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974349Z',\n", - " 'Value': 40.17522,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR056.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7304408Z',\n", - " 'Value': 38.120895,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR058.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344077Z',\n", - " 'Value': 38.930454,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR057.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124171Z',\n", - " 'Value': 40.121517,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR059.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7064135Z',\n", - " 'Value': 39.926296,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR060.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344234Z',\n", - " 'Value': 40.13747,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR041.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7184091Z',\n", - " 'Value': 39.453804,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR042.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344183Z',\n", - " 'Value': 39.231907,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR044.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344203Z',\n", - " 'Value': 39.209846,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR043.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7054259Z',\n", - " 'Value': 38.497158,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR045.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7304277Z',\n", - " 'Value': 39.8807,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR046.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344206Z',\n", - " 'Value': 39.002983,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR048.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974365Z',\n", - " 'Value': 38.303642,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR047.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014289Z',\n", - " 'Value': 38.34986,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR049.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6960056Z',\n", - " 'Value': 40.061825,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR050.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344212Z',\n", - " 'Value': 38.92439,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR081.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344286Z',\n", - " 'Value': 38.967426,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR082.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124088Z',\n", - " 'Value': 39.281673,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR084.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7254062Z',\n", - " 'Value': 38.76199,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR083.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.708429Z',\n", - " 'Value': 39.0392,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR085.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7234315Z',\n", - " 'Value': 39.83571,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR086.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344051Z',\n", - " 'Value': 38.62964,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR088.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7284067Z',\n", - " 'Value': 38.183025,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR087.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.696364Z',\n", - " 'Value': 38.6939,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR089.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344294Z',\n", - " 'Value': 39.701763,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR090.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974364Z',\n", - " 'Value': 38.381435,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR091.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344295Z',\n", - " 'Value': 39.616196,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR092.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344162Z',\n", - " 'Value': 38.873108,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR094.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344297Z',\n", - " 'Value': 39.91545,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR093.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154043Z',\n", - " 'Value': 38.477512,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR095.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344285Z',\n", - " 'Value': 40.136436,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR096.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940339Z',\n", - " 'Value': 38.663403,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR098.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294086Z',\n", - " 'Value': 38.439774,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR097.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.693015Z',\n", - " 'Value': 39.877457,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR099.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7272734Z',\n", - " 'Value': 39.851204,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR100.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344102Z',\n", - " 'Value': 40.158344,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR101.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124251Z',\n", - " 'Value': 38.515728,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR102.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344244Z',\n", - " 'Value': 38.196472,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR104.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344091Z',\n", - " 'Value': 38.189045,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR103.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344243Z',\n", - " 'Value': 38.632553,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR105.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7214259Z',\n", - " 'Value': 39.659435,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR106.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344238Z',\n", - " 'Value': 40.024128,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR108.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7104255Z',\n", - " 'Value': 39.6268,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR107.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344233Z',\n", - " 'Value': 39.79332,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR109.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7304531Z',\n", - " 'Value': 39.44428,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR110.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6910044Z',\n", - " 'Value': 38.394817,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR111.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344221Z',\n", - " 'Value': 39.986492,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR112.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734423Z',\n", - " 'Value': 40.03399,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR114.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7124071Z',\n", - " 'Value': 39.755226,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR113.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344146Z',\n", - " 'Value': 40.04204,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR115.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6920394Z',\n", - " 'Value': 39.835716,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR116.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344217Z',\n", - " 'Value': 39.754185,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR118.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134142Z',\n", - " 'Value': 39.999294,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR117.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344084Z',\n", - " 'Value': 38.19237,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR119.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930219Z',\n", - " 'Value': 39.587517,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR120.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7164391Z',\n", - " 'Value': 39.917152,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR131.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344267Z',\n", - " 'Value': 38.280064,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR132.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963713Z',\n", - " 'Value': 38.88264,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR133.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344271Z',\n", - " 'Value': 39.1591,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR134.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294527Z',\n", - " 'Value': 38.37737,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR135.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7014462Z',\n", - " 'Value': 38.770775,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR136.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7203995Z',\n", - " 'Value': 38.494747,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR137.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344072Z',\n", - " 'Value': 38.563236,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR138.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344106Z',\n", - " 'Value': 38.59732,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR139.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7224Z',\n", - " 'Value': 39.523342,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR140.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7034422Z',\n", - " 'Value': 38.517887,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR121.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.72945Z',\n", - " 'Value': 39.261375,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR122.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963812Z',\n", - " 'Value': 38.122307,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR123.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963886Z',\n", - " 'Value': 38.365185,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR124.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344137Z',\n", - " 'Value': 38.395332,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR125.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344134Z',\n", - " 'Value': 38.455627,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR126.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344131Z',\n", - " 'Value': 38.452053,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR127.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344129Z',\n", - " 'Value': 38.512604,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR128.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314364Z',\n", - " 'Value': 38.163208,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR129.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734428Z',\n", - " 'Value': 39.90843,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR130.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344275Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR242.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004019Z',\n", - " 'Value': 38.816906,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR241.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344195Z',\n", - " 'Value': 38.80518,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR243.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344188Z',\n", - " 'Value': 40.1562,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR244.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244217Z',\n", - " 'Value': 38.288567,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR246.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344201Z',\n", - " 'Value': 39.96346,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR245.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930369Z',\n", - " 'Value': 38.370255,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR247.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7074223Z',\n", - " 'Value': 38.583477,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR248.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7154373Z',\n", - " 'Value': 38.079945,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR250.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344083Z',\n", - " 'Value': 38.294643,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR249.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344334Z',\n", - " 'Value': 40.164425,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR252.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7304554Z',\n", - " 'Value': 39.0754,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR251.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344216Z',\n", - " 'Value': 40.179806,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR253.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6984011Z',\n", - " 'Value': 38.404854,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR254.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344144Z',\n", - " 'Value': 38.72932,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR256.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344219Z',\n", - " 'Value': 39.478455,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR255.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344228Z',\n", - " 'Value': 38.613785,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR257.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344178Z',\n", - " 'Value': 39.513885,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR258.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344367Z',\n", - " 'Value': 40.09816,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR260.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344317Z',\n", - " 'Value': 38.45213,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR259.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7134265Z',\n", - " 'Value': 40.090103,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR262.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344113Z',\n", - " 'Value': 25.000477,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR261.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244233Z',\n", - " 'Value': 24.251741,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR263.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344321Z',\n", - " 'Value': 23.701147,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR264.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940478Z',\n", - " 'Value': 24.541632,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR266.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344327Z',\n", - " 'Value': 23.477318,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR265.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344323Z',\n", - " 'Value': 24.753544,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR267.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344325Z',\n", - " 'Value': 24.724838,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR268.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344218Z',\n", - " 'Value': 24.80207,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR270.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344339Z',\n", - " 'Value': 25.085176,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR269.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7314276Z',\n", - " 'Value': 24.138388,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR272.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244115Z',\n", - " 'Value': 24.278738,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR271.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344336Z',\n", - " 'Value': 23.923967,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR273.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344335Z',\n", - " 'Value': 23.215237,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR274.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344345Z',\n", - " 'Value': 24.152054,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR276.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7004027Z',\n", - " 'Value': 24.990276,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR275.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940062Z',\n", - " 'Value': 24.489922,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR277.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7094139Z',\n", - " 'Value': 24.46337,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR278.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344368Z',\n", - " 'Value': 24.551352,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR280.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344287Z',\n", - " 'Value': 25.195246,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR279.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.709427Z',\n", - " 'Value': 23.609083,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR232.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7284497Z',\n", - " 'Value': 39.403683,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR231.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344236Z',\n", - " 'Value': 39.96489,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR233.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344274Z',\n", - " 'Value': 39.986343,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR234.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344071Z',\n", - " 'Value': 39.628845,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR236.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344259Z',\n", - " 'Value': 39.269802,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR235.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344265Z',\n", - " 'Value': 38.74258,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR237.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344261Z',\n", - " 'Value': 40.024624,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR238.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344365Z',\n", - " 'Value': 40.317173,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR240.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344181Z',\n", - " 'Value': 39.013767,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR239.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7274155Z',\n", - " 'Value': 39.812885,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR222.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.701408Z',\n", - " 'Value': 39.133728,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR221.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6984281Z',\n", - " 'Value': 38.27586,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR223.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7184391Z',\n", - " 'Value': 40.24217,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR224.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963908Z',\n", - " 'Value': 39.39085,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR226.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344232Z',\n", - " 'Value': 40.151733,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR225.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734409Z',\n", - " 'Value': 39.305435,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR227.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344237Z',\n", - " 'Value': 39.597782,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR228.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7094363Z',\n", - " 'Value': 40.057266,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR230.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734427Z',\n", - " 'Value': 39.52029,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR229.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7024202Z',\n", - " 'Value': 39.219227,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR212.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344115Z',\n", - " 'Value': 38.48031,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR211.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930374Z',\n", - " 'Value': 39.97267,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR213.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734415Z',\n", - " 'Value': 38.208794,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR214.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930002Z',\n", - " 'Value': 39.026394,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR216.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6940152Z',\n", - " 'Value': 39.156128,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR215.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7114368Z',\n", - " 'Value': 38.513325,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR217.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6974046Z',\n", - " 'Value': 39.1014,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR218.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994366Z',\n", - " 'Value': 38.13853,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR220.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344247Z',\n", - " 'Value': 38.9195,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR219.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344362Z',\n", - " 'Value': 40.11748,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR202.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6994289Z',\n", - " 'Value': 38.300476,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR201.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6963786Z',\n", - " 'Value': 40.036777,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR203.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7294349Z',\n", - " 'Value': 40.1832,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR204.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344132Z',\n", - " 'Value': 39.740383,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR206.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7244072Z',\n", - " 'Value': 39.898827,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR205.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7044248Z',\n", - " 'Value': 39.098427,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR207.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.734413Z',\n", - " 'Value': 38.863567,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR208.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7344279Z',\n", - " 'Value': 39.586384,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR210.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.6930201Z',\n", - " 'Value': 40.238556,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB02.TR-TB02.TR209.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7104104Z',\n", - " 'Value': 38.3749,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " {'Id': 'Enterprise.EG-AS.S1.MMSTB01.TR-TB01.TR001.Signals.AngleMeasured',\n", - " 'Namespace': 3,\n", - " 'IdType': 1,\n", - " 'Timestamp': '2024-09-26T11:49:01.7273647Z',\n", - " 'Value': 0,\n", - " 'ValueType': 'Float',\n", - " 'StatusCode': None,\n", - " 'StatusSymbol': None},\n", - " ...]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Live value data of trackers\n", "live_value = opc_data.get_values(\n", @@ -9177,215 +268,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TimestampValueTypeValueStatusCodeStatusSymbolIdTypeIdNamespace
02024-08-27T13:49:34.283241ZDouble2420.1223141Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
12024-08-27T13:50:34.283241ZDouble2926.2788091Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
22024-08-27T13:51:34.283241ZDouble2981.6279301Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
32024-08-27T13:52:34.283241ZDouble3375.9743651Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
42024-08-27T13:53:34.283241ZDouble3476.2563481Good1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
...........................
42307152024-08-28T13:44:34.283241ZDouble6677.9018551Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307162024-08-28T13:45:34.283241ZDouble6677.9018551Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307172024-08-28T13:46:34.283241ZDouble5503.1015621Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307182024-08-28T13:47:34.283241ZDouble3167.9548341Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42307192024-08-28T13:48:34.283241ZDouble5536.0825201Good1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
\n", - "

4230720 rows × 8 columns

\n", - "
" - ], - "text/plain": [ - " Timestamp ValueType Value StatusCode \\\n", - "0 2024-08-27T13:49:34.283241Z Double 2420.122314 1 \n", - "1 2024-08-27T13:50:34.283241Z Double 2926.278809 1 \n", - "2 2024-08-27T13:51:34.283241Z Double 2981.627930 1 \n", - "3 2024-08-27T13:52:34.283241Z Double 3375.974365 1 \n", - "4 2024-08-27T13:53:34.283241Z Double 3476.256348 1 \n", - "... ... ... ... ... \n", - "4230715 2024-08-28T13:44:34.283241Z Double 6677.901855 1 \n", - "4230716 2024-08-28T13:45:34.283241Z Double 6677.901855 1 \n", - "4230717 2024-08-28T13:46:34.283241Z Double 5503.101562 1 \n", - "4230718 2024-08-28T13:47:34.283241Z Double 3167.954834 1 \n", - "4230719 2024-08-28T13:48:34.283241Z Double 5536.082520 1 \n", - "\n", - " StatusSymbol IdType \\\n", - "0 Good 1 \n", - "1 Good 1 \n", - "2 Good 1 \n", - "3 Good 1 \n", - "4 Good 1 \n", - "... ... ... \n", - "4230715 Good 1 \n", - "4230716 Good 1 \n", - "4230717 Good 1 \n", - "4230718 Good 1 \n", - "4230719 Good 1 \n", - "\n", - " Id Namespace \n", - "0 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "1 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "2 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "3 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "4 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "... ... ... \n", - "4230715 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4230716 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4230717 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4230718 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4230719 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "\n", - "[4230720 rows x 8 columns]" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# 1 day aggregated historical data\n", "one_day_historical_data = opc_data.get_historical_aggregated_values(\n", @@ -9400,215 +285,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
TimestampValueTypeValueStatusCode.CodeStatusCode.SymbolIdTypeIdNamespace
02024-07-13T00:00:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
12024-07-13T00:01:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
22024-07-13T00:02:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
32024-07-13T00:03:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
42024-07-13T00:04:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign...3
...........................
42277772024-07-13T23:54:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277782024-07-13T23:55:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277792024-07-13T23:56:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277802024-07-13T23:57:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
42277812024-07-13T23:58:00ZDouble0.01.083507e+09UncertainSubNormal1Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign...3
\n", - "

4227782 rows × 8 columns

\n", - "
" - ], - "text/plain": [ - " Timestamp ValueType Value StatusCode.Code \\\n", - "0 2024-07-13T00:00:00Z Double 0.0 1.083507e+09 \n", - "1 2024-07-13T00:01:00Z Double 0.0 1.083507e+09 \n", - "2 2024-07-13T00:02:00Z Double 0.0 1.083507e+09 \n", - "3 2024-07-13T00:03:00Z Double 0.0 1.083507e+09 \n", - "4 2024-07-13T00:04:00Z Double 0.0 1.083507e+09 \n", - "... ... ... ... ... \n", - "4227777 2024-07-13T23:54:00Z Double 0.0 1.083507e+09 \n", - "4227778 2024-07-13T23:55:00Z Double 0.0 1.083507e+09 \n", - "4227779 2024-07-13T23:56:00Z Double 0.0 1.083507e+09 \n", - "4227780 2024-07-13T23:57:00Z Double 0.0 1.083507e+09 \n", - "4227781 2024-07-13T23:58:00Z Double 0.0 1.083507e+09 \n", - "\n", - " StatusCode.Symbol IdType \\\n", - "0 UncertainSubNormal 1 \n", - "1 UncertainSubNormal 1 \n", - "2 UncertainSubNormal 1 \n", - "3 UncertainSubNormal 1 \n", - "4 UncertainSubNormal 1 \n", - "... ... ... \n", - "4227777 UncertainSubNormal 1 \n", - "4227778 UncertainSubNormal 1 \n", - "4227779 UncertainSubNormal 1 \n", - "4227780 UncertainSubNormal 1 \n", - "4227781 UncertainSubNormal 1 \n", - "\n", - " Id Namespace \n", - "0 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "1 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "2 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "3 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "4 Enterprise.EG-AS.S1.Z5.TS01.I01.SM01.CH01.Sign... 3 \n", - "... ... ... \n", - "4227777 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4227778 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4227779 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4227780 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "4227781 Enterprise.EG-AS.S1.Z1.TS11.I22.SM13.CH09.Sign... 3 \n", - "\n", - "[4227782 rows x 8 columns]" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# 1 day raw historical data\n", "one_day_raw_historical_data = opc_data.get_raw_historical_values(\n", diff --git a/src/pyprediktormapclient/auth_client.py b/src/pyprediktormapclient/auth_client.py index ed9765b..d044f42 100644 --- a/src/pyprediktormapclient/auth_client.py +++ b/src/pyprediktormapclient/auth_client.py @@ -5,6 +5,8 @@ import requests import json import re +from dateutil.parser import parse, ParserError +from dateutil.tz import tzutc class Ory_Login_Structure(BaseModel): @@ -110,23 +112,19 @@ def get_login_token(self) -> None: # Return if no content from server if not isinstance(content.get("session_token"), str): raise RuntimeError(content.get("ErrorMessage")) - self.token = Token(session_token=content.get("session_token")) + + session_token = content.get("session_token") + expires_at = None # Check if token has expiry date, save it if it does expires_at_str = content.get("session", {}).get("expires_at") if isinstance(expires_at_str, str): try: - expires_at = datetime.datetime.fromisoformat( - expires_at_str.replace("Z", "+00:00") - ) - self.token = Token( - session_token=self.token.session_token, - expires_at=expires_at, - ) - except Exception: - # If string returned from Ory cant be parsed, still should be possible to use Ory, - # might be a setting in Ory to not return expiry date - self.token = Token(session_token=self.token.session_token) + expires_at = parse(expires_at_str).replace(tzinfo=tzutc()) + except ParserError: + expires_at = None + + self.token = Token(session_token=session_token, expires_at=expires_at) def check_if_token_has_expired(self) -> bool: """Check if token has expired.""" diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index a07bcdd..f780e60 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -5,8 +5,6 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone import requests -from dateutil.parser import parse -from dateutil.tz import tzutc from pyprediktormapclient.auth_client import AUTH_CLIENT, Token @@ -438,30 +436,11 @@ def test_get_self_service_login_token_successful( auth_client.id = auth_id auth_client.get_login_token() - print(f"Auth client token: {auth_client.token}") - print(f"Auth client token expires_at: {auth_client.token.expires_at}") - - expected_expires_at = parse(auth_expires_at).replace(tzinfo=tzutc()) - print(f"Expected expires_at: {expected_expires_at}") - test_token = Token( - session_token=auth_session_id, expires_at=expected_expires_at + session_token=auth_session_id, expires_at=auth_expires_at ) - - assert auth_client.token.session_token == test_token.session_token, \ - f"Expected session_token: {test_token.session_token}, got: {auth_client.token.session_token}" - - if auth_client.token.expires_at is None: - print("Mocked response content:") - print(successful_self_service_login_token_mocked_response) - assert False, "Expected expires_at to be set, but it was None" - else: - actual_expires_at = auth_client.token.expires_at - if actual_expires_at.tzinfo is None: - actual_expires_at = actual_expires_at.replace(tzinfo=tzutc()) - - assert actual_expires_at == expected_expires_at, \ - f"Expected {expected_expires_at}, but got {actual_expires_at}" + assert auth_client.token.session_token == test_token.session_token + assert auth_client.token.expires_at == test_token.expires_at @patch( "requests.post", From 39c67a451893ad5749c858058e637cf4edd3a31d Mon Sep 17 00:00:00 2001 From: MeenBna Date: Fri, 27 Sep 2024 14:54:38 +0200 Subject: [PATCH 32/35] Updated README with pre-commit hook details. --- README.md | 54 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 883ba95..c182723 100644 --- a/README.md +++ b/README.md @@ -40,18 +40,42 @@ source .venv/bin/activate pip install -r requirements.txt ``` +## Pre-commit Hooks + +To ensure code quality and maintain a consistent coding standard, pre-commit hooks have been added to the repository. All the pre-commit checks must be passed before committing your changes. Follow the instructions below to install and use them. + +4. Activate pre-commit hooks +After installing the requirements, activate the pre-commit hooks by running: + +``` +pre-commit install +``` + +5. Run pre-commit hooks manually +It's recommended to run the pre-commit hooks before making a commit to catch issues early. Use the following command to run all the hooks manually: + +``` +pre-commit run --all-files +``` + +6. Stage your changes +After making the required changes and ensuring they pass the pre-commit hooks, add the changes to the staging area before creating a commit: + +``` +git add . +``` + ## Run and build -4. Run tests +7. Run tests ``` tox ``` -5. Do your changes - Add your changes and create a new PR to be approved. +8. Make your changes and submit a new PR for approval. -6. Build +9. Build ``` tox -e build @@ -59,16 +83,16 @@ tox -e build ## Changes -7. Please apply your changes. If they will facilitate the work of the person using pyPrediktorMapClient, especially the new features you've implemented, ensure that you describe your changes comprehensively and provide guidance in the README.md file under the chapter `Manual - How to Use` (check below). +10. Please apply your changes. If they will facilitate the work of the person using pyPrediktorMapClient, especially the new features you've implemented, ensure that you describe your changes comprehensively and provide guidance in the README.md file under the chapter `Manual - How to Use` (check below). -8. Commit your changes to a new branch, push and create a new pull request for review. +11. Commit your changes to a new branch, push and create a new pull request for review. ## Publish on PyPi -9. Open [https://pypi.org/](https://pypi.org/) and log in. -10. Open [https://pypi.org/manage/account/](https://pypi.org/manage/account/) and generate a new API token but only if you don't have one already. Keep the API key on your local machine because once generated it will be visible only once. Delete API keys that are no longer used! -11. In your code editor, open the root directory of the current project and clean the content of folder `dist`. -12. Create a new tag and push it to the GitHub repository. For instance, if the latest tag is `0.6.7` the new tag should be `0.6.8`. Alternatively, if the changes are major, you can set the new tag to `0.7.0`. +12. Open [https://pypi.org/](https://pypi.org/) and log in. +13. Open [https://pypi.org/manage/account/](https://pypi.org/manage/account/) and generate a new API token but only if you don't have one already. Keep the API key on your local machine because once generated it will be visible only once. Delete API keys that are no longer used! +14. In your code editor, open the root directory of the current project and clean the content of folder `dist`. +15. Create a new tag and push it to the GitHub repository. For instance, if the latest tag is `0.6.7` the new tag should be `0.6.8`. Alternatively, if the changes are major, you can set the new tag to `0.7.0`. Use the following commands to create the tag and to publish it. @@ -77,13 +101,13 @@ git tag 0.6.8 git push origin 0.6.8 ``` -13. Create a new build. Be aware that `tox.ini` file is configured in a way to get latest tag from the repository. That tag is going to be used to label the new build. +16. Create a new build. Be aware that `tox.ini` file is configured in a way to get latest tag from the repository. That tag is going to be used to label the new build. ``` tox -e build ``` -14. Be sure that twine is installed. If not, run the following command: +17. Be sure that twine is installed. If not, run the following command: ``` python3 -m pip install twine @@ -95,7 +119,7 @@ or python -m pip install twine ``` -15. Publish the build on PyPi: +18. Publish the build on PyPi: ``` python3 -m twine upload -u __token__ -p API_KEY dist/* @@ -103,11 +127,11 @@ python3 -m twine upload -u __token__ -p API_KEY dist/* Replace API_KEY with the API key you generated earlier or a key you already have. -16. Check if the new version has been release - [Release history](https://pypi.org/project/pyPrediktorMapClient/#history). +19. Check if the new version has been release - [Release history](https://pypi.org/project/pyPrediktorMapClient/#history). Once that's done, we have to publish a new release in the GitHub repository. -17. Open the list of released published on the [GitHub repository](https://github.com/PrediktorAS/pyPrediktorMapClient/releases). Draft a new release by pressing "Draft a new release". +20. Open the list of released published on the [GitHub repository](https://github.com/PrediktorAS/pyPrediktorMapClient/releases). Draft a new release by pressing "Draft a new release". Use the newly created tag which in our example is 0.6.8. Add detailed descrption about the new changes. From b0a6ec8fff794e23146f655eaba3bb40c3f3a14a Mon Sep 17 00:00:00 2001 From: MeenBna Date: Sun, 29 Sep 2024 10:37:35 +0200 Subject: [PATCH 33/35] Fixed failing tests after resolving merge conflicts. --- tests/opc_ua_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index b224cab..14266c0 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1208,7 +1208,7 @@ async def make_historical_request(opc): async def make_raw_historical_request(opc): - return await opc.get_raw_historical_values_asyn( + return await opc.get_historical_raw_values_asyn( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), variable_list=list_of_ids, @@ -1414,12 +1414,12 @@ async def test_get_historical_values_no_results(self, mock_post): assert result.empty @patch("aiohttp.ClientSession.post") - async def test_get_raw_historical_values_asyn(self, mock_post): + async def test_get_historical_raw_values_asyn(self, mock_post): mock_post.return_value = AsyncMockResponse( json_data=successful_raw_historical_result, status_code=200 ) opc = OPC_UA(rest_url=URL, opcua_url=OPC_URL) - result = await opc.get_raw_historical_values_asyn( + result = await opc.get_historical_raw_values_asyn( start_time=datetime(2023, 1, 1), end_time=datetime(2023, 1, 2), variable_list=["SOMEID"], @@ -1435,13 +1435,13 @@ async def test_get_raw_historical_values_success(self): with patch.object( self.opc, - "get_raw_historical_values_asyn", + "get_historical_raw_values_asyn", return_value=mock_result, ): with patch.object( self.opc.helper, "run_coroutine", return_value=mock_result ) as mock_run_coroutine: - result = self.opc.get_raw_historical_values( + result = self.opc.get_historical_raw_values( start_time=(datetime.now() - timedelta(30)), end_time=(datetime.now() - timedelta(29)), variable_list=list_of_ids, @@ -1455,7 +1455,7 @@ async def test_get_raw_historical_values_with_args(self): with patch.object( self.opc, - "get_raw_historical_values_asyn", + "get_historical_raw_values_asyn", return_value=mock_result, ) as mock_async: result = await make_raw_historical_request(self.opc) @@ -1470,7 +1470,7 @@ async def test_get_raw_historical_values_with_args(self): async def test_get_raw_historical_values_exception(self): with patch.object( self.opc, - "get_raw_historical_values_asyn", + "get_historical_raw_values_asyn", side_effect=Exception("Test exception"), ): with pytest.raises(Exception, match="Test exception"): From c68d3e5d4132710e2ed95ad9d88eb339f3f9e4a8 Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 30 Sep 2024 11:26:30 +0200 Subject: [PATCH 34/35] Added isort for sorting Import statements. --- .pre-commit-config.yaml | 11 +++++ README.md | 42 ++++++++++++------- docs/conf.py | 2 +- .../get_and write_historical_opc_ua_values.py | 4 +- examples/get_and write_opc_ua_values.py | 4 +- .../get_historical_opc_ua_values_with_auth.py | 3 +- pyproject.toml | 5 +++ requirements.txt | 1 + src/pyprediktormapclient/__init__.py | 8 ++-- src/pyprediktormapclient/analytics_helper.py | 6 +-- src/pyprediktormapclient/auth_client.py | 12 +++--- .../dwh/context/enercast.py | 3 +- src/pyprediktormapclient/dwh/context/plant.py | 3 +- .../dwh/context/solcast.py | 3 +- src/pyprediktormapclient/dwh/db.py | 5 ++- src/pyprediktormapclient/dwh/dwh.py | 5 ++- src/pyprediktormapclient/dwh/idwh.py | 2 +- src/pyprediktormapclient/model_index.py | 4 +- src/pyprediktormapclient/opc_ua.py | 22 +++++----- src/pyprediktormapclient/shared.py | 3 +- tests/analytics_helper_test.py | 1 + tests/auth_client_test.py | 7 ++-- tests/conftest.py | 12 +++--- tests/dwh/context/enercast_test.py | 3 +- tests/dwh/context/plant_test.py | 3 +- tests/dwh/context/solcast_test.py | 3 +- tests/dwh/db_test.py | 12 +++--- tests/dwh/dwh_test.py | 10 +++-- tests/model_index_test.py | 14 ++++--- tests/opc_ua_test.py | 25 +++++------ tests/shared_test.py | 4 +- 31 files changed, 151 insertions(+), 91 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 264e773..7b4792d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,28 +12,39 @@ repos: - id: requirements-txt-fixer - id: debug-statements - id: name-tests-test + - repo: https://github.com/PyCQA/docformatter rev: v1.7.5 hooks: - id: docformatter args: ["--in-place"] + - repo: https://github.com/PyCQA/autoflake rev: v2.3.1 hooks: - id: autoflake args: ["--remove-all-unused-imports", "--in-place"] + - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black args: ["--line-length", "79"] + - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 hooks: - id: flake8 args: ["--ignore=E501,W503"] + - repo: https://github.com/PyCQA/bandit rev: 1.7.5 hooks: - id: bandit args: ["-x", "tests/*"] + +- repo: https://github.com/pre-commit/mirrors-isort + rev: v5.9.0 + hooks: + - id: isort + args: ["--profile", "black", "--line-length", "79"] diff --git a/README.md b/README.md index c182723..a217647 100644 --- a/README.md +++ b/README.md @@ -65,17 +65,31 @@ After making the required changes and ensuring they pass the pre-commit hooks, a git add . ``` +## isort for Import Sorting + +To maintain a clean and organized codebase, we use isort to automatically sort Python import statements according to PEP 8 standards. + +7. Run isort + +Run `isort` manually to sort the imports in the project using the command: + +``` +isort . +``` +The `isort` configuration is managed in the `pyproject.toml` file to ensure it integrates well with black and other tools. + + ## Run and build -7. Run tests +8. Run tests ``` tox ``` -8. Make your changes and submit a new PR for approval. +9. Make your changes and submit a new PR for approval. -9. Build +10. Build ``` tox -e build @@ -83,16 +97,16 @@ tox -e build ## Changes -10. Please apply your changes. If they will facilitate the work of the person using pyPrediktorMapClient, especially the new features you've implemented, ensure that you describe your changes comprehensively and provide guidance in the README.md file under the chapter `Manual - How to Use` (check below). +11. Please apply your changes. If they will facilitate the work of the person using pyPrediktorMapClient, especially the new features you've implemented, ensure that you describe your changes comprehensively and provide guidance in the README.md file under the chapter `Manual - How to Use` (check below). -11. Commit your changes to a new branch, push and create a new pull request for review. +12. Commit your changes to a new branch, push and create a new pull request for review. ## Publish on PyPi -12. Open [https://pypi.org/](https://pypi.org/) and log in. -13. Open [https://pypi.org/manage/account/](https://pypi.org/manage/account/) and generate a new API token but only if you don't have one already. Keep the API key on your local machine because once generated it will be visible only once. Delete API keys that are no longer used! -14. In your code editor, open the root directory of the current project and clean the content of folder `dist`. -15. Create a new tag and push it to the GitHub repository. For instance, if the latest tag is `0.6.7` the new tag should be `0.6.8`. Alternatively, if the changes are major, you can set the new tag to `0.7.0`. +13. Open [https://pypi.org/](https://pypi.org/) and log in. +14. Open [https://pypi.org/manage/account/](https://pypi.org/manage/account/) and generate a new API token but only if you don't have one already. Keep the API key on your local machine because once generated it will be visible only once. Delete API keys that are no longer used! +15. In your code editor, open the root directory of the current project and clean the content of folder `dist`. +16. Create a new tag and push it to the GitHub repository. For instance, if the latest tag is `0.6.7` the new tag should be `0.6.8`. Alternatively, if the changes are major, you can set the new tag to `0.7.0`. Use the following commands to create the tag and to publish it. @@ -101,13 +115,13 @@ git tag 0.6.8 git push origin 0.6.8 ``` -16. Create a new build. Be aware that `tox.ini` file is configured in a way to get latest tag from the repository. That tag is going to be used to label the new build. +17. Create a new build. Be aware that `tox.ini` file is configured in a way to get latest tag from the repository. That tag is going to be used to label the new build. ``` tox -e build ``` -17. Be sure that twine is installed. If not, run the following command: +18. Be sure that twine is installed. If not, run the following command: ``` python3 -m pip install twine @@ -119,7 +133,7 @@ or python -m pip install twine ``` -18. Publish the build on PyPi: +19. Publish the build on PyPi: ``` python3 -m twine upload -u __token__ -p API_KEY dist/* @@ -127,11 +141,11 @@ python3 -m twine upload -u __token__ -p API_KEY dist/* Replace API_KEY with the API key you generated earlier or a key you already have. -19. Check if the new version has been release - [Release history](https://pypi.org/project/pyPrediktorMapClient/#history). +20. Check if the new version has been release - [Release history](https://pypi.org/project/pyPrediktorMapClient/#history). Once that's done, we have to publish a new release in the GitHub repository. -20. Open the list of released published on the [GitHub repository](https://github.com/PrediktorAS/pyPrediktorMapClient/releases). Draft a new release by pressing "Draft a new release". +21. Open the list of released published on the [GitHub repository](https://github.com/PrediktorAS/pyPrediktorMapClient/releases). Draft a new release by pressing "Draft a new release". Use the newly created tag which in our example is 0.6.8. Add detailed descrption about the new changes. diff --git a/docs/conf.py b/docs/conf.py index 61cfbeb..0c1818f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,8 +8,8 @@ # serve to show the default. import os -import sys import shutil +import sys # -- Path setup -------------------------------------------------------------- diff --git a/examples/get_and write_historical_opc_ua_values.py b/examples/get_and write_historical_opc_ua_values.py index 094b2db..74c5196 100644 --- a/examples/get_and write_historical_opc_ua_values.py +++ b/examples/get_and write_historical_opc_ua_values.py @@ -4,10 +4,10 @@ # Import OPC UA functions from pyprediktormapclient.opc_ua import ( OPC_UA, + SubValue, + Value, Variables, WriteHistoricalVariables, - Value, - SubValue, ) diff --git a/examples/get_and write_opc_ua_values.py b/examples/get_and write_opc_ua_values.py index c0077e9..3cd5a62 100644 --- a/examples/get_and write_opc_ua_values.py +++ b/examples/get_and write_opc_ua_values.py @@ -1,10 +1,10 @@ # Import OPC UA functions from pyprediktormapclient.opc_ua import ( OPC_UA, + SubValue, + Value, Variables, WriteVariables, - Value, - SubValue, ) diff --git a/examples/get_historical_opc_ua_values_with_auth.py b/examples/get_historical_opc_ua_values_with_auth.py index 9be80d9..50cfbfb 100644 --- a/examples/get_historical_opc_ua_values_with_auth.py +++ b/examples/get_historical_opc_ua_values_with_auth.py @@ -2,9 +2,10 @@ import datetime import json +from pyprediktormapclient.auth_client import AUTH_CLIENT + # Import OPC UA functions from pyprediktormapclient.opc_ua import OPC_UA, Variables -from pyprediktormapclient.auth_client import AUTH_CLIENT def main(): diff --git a/pyproject.toml b/pyproject.toml index 0868715..2294f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,3 +13,8 @@ pythonpath = [ "src" ] asyncio_default_fixture_loop_scope = "function" + +[tool.isort] +profile = "black" +line_length = 79 +known_first_party = ["src"] diff --git a/requirements.txt b/requirements.txt index 2724d7d..40624fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -e . ipykernel +isort nest_asyncio pre-commit pytest-asyncio diff --git a/src/pyprediktormapclient/__init__.py b/src/pyprediktormapclient/__init__.py index 19db91b..0ed7c95 100644 --- a/src/pyprediktormapclient/__init__.py +++ b/src/pyprediktormapclient/__init__.py @@ -2,15 +2,15 @@ if sys.version_info[:2] >= (3, 8): # TODO: Import directly (no need for conditional) when `python_requires = >= 3.9` - from importlib.metadata import ( + from importlib.metadata import ( # pragma: no cover PackageNotFoundError, version, - ) # pragma: no cover + ) else: - from importlib_metadata import ( + from importlib_metadata import ( # pragma: no cover PackageNotFoundError, version, - ) # pragma: no cover + ) try: # Change here if project is renamed and does not equal the package name diff --git a/src/pyprediktormapclient/analytics_helper.py b/src/pyprediktormapclient/analytics_helper.py index f8c47a7..f848594 100644 --- a/src/pyprediktormapclient/analytics_helper.py +++ b/src/pyprediktormapclient/analytics_helper.py @@ -1,9 +1,9 @@ -import pandas as pd import logging import re -from typing import List, Any -from pydantic import validate_call +from typing import Any, List +import pandas as pd +from pydantic import validate_call logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) diff --git a/src/pyprediktormapclient/auth_client.py b/src/pyprediktormapclient/auth_client.py index d044f42..f43b049 100644 --- a/src/pyprediktormapclient/auth_client.py +++ b/src/pyprediktormapclient/auth_client.py @@ -1,12 +1,14 @@ -from pydantic import BaseModel, AnyUrl, field_validator, ConfigDict -from typing import Optional -from pyprediktormapclient.shared import request_from_api import datetime -import requests import json import re -from dateutil.parser import parse, ParserError +from typing import Optional + +import requests +from dateutil.parser import ParserError, parse from dateutil.tz import tzutc +from pydantic import AnyUrl, BaseModel, ConfigDict, field_validator + +from pyprediktormapclient.shared import request_from_api class Ory_Login_Structure(BaseModel): diff --git a/src/pyprediktormapclient/dwh/context/enercast.py b/src/pyprediktormapclient/dwh/context/enercast.py index 7092f97..d2c9b9d 100644 --- a/src/pyprediktormapclient/dwh/context/enercast.py +++ b/src/pyprediktormapclient/dwh/context/enercast.py @@ -1,6 +1,7 @@ import json +from typing import Dict, List, Union + from pydantic import validate_call -from typing import List, Dict, Union from ..idwh import IDWH diff --git a/src/pyprediktormapclient/dwh/context/plant.py b/src/pyprediktormapclient/dwh/context/plant.py index c7dd85d..054bcef 100644 --- a/src/pyprediktormapclient/dwh/context/plant.py +++ b/src/pyprediktormapclient/dwh/context/plant.py @@ -1,6 +1,7 @@ import json +from typing import Dict, List + from pydantic import validate_call -from typing import List, Dict from ..idwh import IDWH diff --git a/src/pyprediktormapclient/dwh/context/solcast.py b/src/pyprediktormapclient/dwh/context/solcast.py index bdbc597..72de05a 100644 --- a/src/pyprediktormapclient/dwh/context/solcast.py +++ b/src/pyprediktormapclient/dwh/context/solcast.py @@ -1,6 +1,7 @@ import json +from typing import Dict, List, Union + from pydantic import validate_call -from typing import List, Dict, Union from ..idwh import IDWH diff --git a/src/pyprediktormapclient/dwh/db.py b/src/pyprediktormapclient/dwh/db.py index 0bb124f..25101ba 100644 --- a/src/pyprediktormapclient/dwh/db.py +++ b/src/pyprediktormapclient/dwh/db.py @@ -1,7 +1,8 @@ -import pyodbc import logging +from typing import Any, List + import pandas as pd -from typing import List, Any +import pyodbc from pydantic import validate_call logger = logging.getLogger(__name__) diff --git a/src/pyprediktormapclient/dwh/dwh.py b/src/pyprediktormapclient/dwh/dwh.py index 4ca159e..e2daada 100644 --- a/src/pyprediktormapclient/dwh/dwh.py +++ b/src/pyprediktormapclient/dwh/dwh.py @@ -1,7 +1,8 @@ -import pkgutil -import logging import importlib +import logging +import pkgutil from typing import Dict + from pydantic import validate_call from pyprediktorutilities.dwh.dwh import Dwh as Db diff --git a/src/pyprediktormapclient/dwh/idwh.py b/src/pyprediktormapclient/dwh/idwh.py index 2a65b1d..0ed0e84 100644 --- a/src/pyprediktormapclient/dwh/idwh.py +++ b/src/pyprediktormapclient/dwh/idwh.py @@ -1,5 +1,5 @@ -from typing import Dict, List from abc import ABC, abstractmethod +from typing import Dict, List class IDWH(ABC): diff --git a/src/pyprediktormapclient/model_index.py b/src/pyprediktormapclient/model_index.py index 10d4af4..de9fad1 100644 --- a/src/pyprediktormapclient/model_index.py +++ b/src/pyprediktormapclient/model_index.py @@ -1,10 +1,12 @@ import json import logging +from datetime import date, datetime from typing import List + import requests -from datetime import date, datetime from pydantic import AnyUrl from pydantic_core import Url + from pyprediktormapclient.shared import request_from_api logger = logging.getLogger(__name__) diff --git a/src/pyprediktormapclient/opc_ua.py b/src/pyprediktormapclient/opc_ua.py index f509ec6..708274f 100644 --- a/src/pyprediktormapclient/opc_ua.py +++ b/src/pyprediktormapclient/opc_ua.py @@ -1,19 +1,21 @@ +import asyncio +import copy import json import logging -import copy +from asyncio import Semaphore +from datetime import date, datetime, timedelta +from typing import Any, Callable, Dict, List, Optional, Union + +import aiohttp +import nest_asyncio import pandas as pd import requests -from datetime import date, datetime, timedelta -from typing import Dict, List, Any, Union, Optional, Callable -from pydantic import BaseModel, AnyUrl +from aiohttp import ClientSession +from pydantic import AnyUrl, BaseModel from pydantic_core import Url -from pyprediktormapclient.shared import request_from_api from requests import HTTPError -import asyncio -import aiohttp -from aiohttp import ClientSession -from asyncio import Semaphore -import nest_asyncio + +from pyprediktormapclient.shared import request_from_api nest_asyncio.apply() diff --git a/src/pyprediktormapclient/shared.py b/src/pyprediktormapclient/shared.py index a8bce3b..beb78d9 100644 --- a/src/pyprediktormapclient/shared.py +++ b/src/pyprediktormapclient/shared.py @@ -1,6 +1,7 @@ +from typing import Literal + import requests from pydantic import AnyUrl, ValidationError -from typing import Literal class Config: diff --git a/tests/analytics_helper_test.py b/tests/analytics_helper_test.py index 0e45328..766e12a 100644 --- a/tests/analytics_helper_test.py +++ b/tests/analytics_helper_test.py @@ -1,4 +1,5 @@ import unittest + import pandas as pd import pytest diff --git a/tests/auth_client_test.py b/tests/auth_client_test.py index f780e60..48a1e6e 100644 --- a/tests/auth_client_test.py +++ b/tests/auth_client_test.py @@ -1,10 +1,11 @@ import unittest -from unittest.mock import patch, Mock -import pytest -from pydantic import ValidationError, BaseModel, AnyUrl from copy import deepcopy from datetime import datetime, timedelta, timezone +from unittest.mock import Mock, patch + +import pytest import requests +from pydantic import AnyUrl, BaseModel, ValidationError from pyprediktormapclient.auth_client import AUTH_CLIENT, Token diff --git a/tests/conftest.py b/tests/conftest.py index 4db5018..b0075b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,14 @@ -import pytest import random import string -from unittest.mock import Mock, patch, AsyncMock +from unittest.mock import AsyncMock, Mock, patch + +import pytest from aiohttp import ClientResponseError -from pyprediktormapclient.opc_ua import OPC_UA -from pyprediktormapclient.dwh.dwh import DWH -from pyprediktormapclient.dwh.db import Db + from pyprediktormapclient.auth_client import AUTH_CLIENT +from pyprediktormapclient.dwh.db import Db +from pyprediktormapclient.dwh.dwh import DWH +from pyprediktormapclient.opc_ua import OPC_UA URL = "http://someserver.somedomain.com/v1/" username = "some@user.com" diff --git a/tests/dwh/context/enercast_test.py b/tests/dwh/context/enercast_test.py index c20b9ad..a71f894 100644 --- a/tests/dwh/context/enercast_test.py +++ b/tests/dwh/context/enercast_test.py @@ -1,7 +1,8 @@ import json from unittest.mock import Mock -from pyprediktormapclient.dwh.idwh import IDWH + from pyprediktormapclient.dwh.context.enercast import Enercast +from pyprediktormapclient.dwh.idwh import IDWH """ __init__ diff --git a/tests/dwh/context/plant_test.py b/tests/dwh/context/plant_test.py index a00c46c..75b327f 100644 --- a/tests/dwh/context/plant_test.py +++ b/tests/dwh/context/plant_test.py @@ -1,7 +1,8 @@ import json from unittest.mock import Mock -from pyprediktormapclient.dwh.idwh import IDWH + from pyprediktormapclient.dwh.context.plant import Plant +from pyprediktormapclient.dwh.idwh import IDWH """ __init__ diff --git a/tests/dwh/context/solcast_test.py b/tests/dwh/context/solcast_test.py index 92330b5..d332ea4 100644 --- a/tests/dwh/context/solcast_test.py +++ b/tests/dwh/context/solcast_test.py @@ -1,7 +1,8 @@ import json from unittest.mock import Mock -from pyprediktormapclient.dwh.idwh import IDWH + from pyprediktormapclient.dwh.context.solcast import Solcast +from pyprediktormapclient.dwh.idwh import IDWH """ __init__ diff --git a/tests/dwh/db_test.py b/tests/dwh/db_test.py index 91849ae..21d1b00 100644 --- a/tests/dwh/db_test.py +++ b/tests/dwh/db_test.py @@ -1,13 +1,15 @@ -import pytest -import pyodbc import inspect +from typing import Any, List, get_args, get_origin +from unittest.mock import Mock, patch + import pandas as pd +import pyodbc +import pytest from conftest import grs -from unittest.mock import Mock, patch -from typing import List, Any, get_origin, get_args +from pandas.testing import assert_frame_equal + from pyprediktormapclient.dwh.db import Db from pyprediktormapclient.dwh.idwh import IDWH -from pandas.testing import assert_frame_equal class TestCaseDB: diff --git a/tests/dwh/dwh_test.py b/tests/dwh/dwh_test.py index 2a475dd..c91fa95 100644 --- a/tests/dwh/dwh_test.py +++ b/tests/dwh/dwh_test.py @@ -1,9 +1,11 @@ -import pytest -import unittest -import inspect import datetime +import inspect +import unittest from typing import Dict -from unittest.mock import patch, MagicMock, call +from unittest.mock import MagicMock, call, patch + +import pytest + from pyprediktormapclient.dwh.dwh import DWH from pyprediktormapclient.dwh.idwh import IDWH diff --git a/tests/model_index_test.py b/tests/model_index_test.py index d1d56a2..aac5f90 100644 --- a/tests/model_index_test.py +++ b/tests/model_index_test.py @@ -1,13 +1,15 @@ -import pytest import unittest +from datetime import date, datetime from unittest.mock import Mock, patch -from pydantic import ValidationError, BaseModel, AnyUrl -from pyprediktormapclient.model_index import ModelIndex -from datetime import datetime, date -from pydantic_core import Url -import requests from urllib.parse import urlparse +import pytest +import requests +from pydantic import AnyUrl, BaseModel, ValidationError +from pydantic_core import Url + +from pyprediktormapclient.model_index import ModelIndex + URL = "http://someserver.somedomain.com/v1/" object_types = [ { diff --git a/tests/opc_ua_test.py b/tests/opc_ua_test.py index 14266c0..8ac72de 100644 --- a/tests/opc_ua_test.py +++ b/tests/opc_ua_test.py @@ -1,23 +1,24 @@ +import asyncio +import json import unittest -from unittest.mock import patch, Mock, AsyncMock -import pytest -from datetime import datetime, timedelta, date +from copy import deepcopy +from datetime import date, datetime, timedelta +from typing import List +from unittest.mock import AsyncMock, Mock, patch + import aiohttp +import pandas as pd import pandas.api.types as ptypes -from pydantic import ValidationError, AnyUrl, BaseModel -from requests.exceptions import HTTPError -from typing import List -from copy import deepcopy -from pydantic_core import Url -import asyncio +import pytest import requests -import json -import pandas as pd from aiohttp.client_exceptions import ClientResponseError +from pydantic import AnyUrl, BaseModel, ValidationError +from pydantic_core import Url +from requests.exceptions import HTTPError from yarl import URL as YarlURL -from pyprediktormapclient.opc_ua import OPC_UA, TYPE_LIST from pyprediktormapclient.auth_client import AUTH_CLIENT, Token +from pyprediktormapclient.opc_ua import OPC_UA, TYPE_LIST URL = "http://someserver.somedomain.com/v1/" OPC_URL = "opc.tcp://nosuchserver.nosuchdomain.com" diff --git a/tests/shared_test.py b/tests/shared_test.py index b9ed6c1..15cf4e7 100644 --- a/tests/shared_test.py +++ b/tests/shared_test.py @@ -1,8 +1,10 @@ import unittest from unittest import mock -import requests + import pytest +import requests from requests.exceptions import RequestException + from pyprediktormapclient.shared import request_from_api URL = "http://someserver.somedomain.com/v1/" From 6d364b68b7d4f32d5f62f0e9564a6b6c8970ce4a Mon Sep 17 00:00:00 2001 From: MeenBna Date: Mon, 30 Sep 2024 12:49:58 +0200 Subject: [PATCH 35/35] Implement PR feedback. --- README.md | 7 +++++++ notebooks/Example_Data_Downloading.ipynb | 2 +- notebooks/Exploring_API_Functions.ipynb | 2 +- notebooks/Exploring_API_Functions_Authentication.ipynb | 4 ++-- src/pyprediktormapclient/analytics_helper.py | 5 +++-- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a217647..b5be2f9 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,13 @@ Run `isort` manually to sort the imports in the project using the command: ``` isort . ``` + +However, applying these changes could lead to conflicts. To verify the import order without making any modifications, use the following command: + +``` +isort . --check-only +``` + The `isort` configuration is managed in the `pyproject.toml` file to ensure it integrates well with black and other tools. diff --git a/notebooks/Example_Data_Downloading.ipynb b/notebooks/Example_Data_Downloading.ipynb index 63a9178..0448470 100644 --- a/notebooks/Example_Data_Downloading.ipynb +++ b/notebooks/Example_Data_Downloading.ipynb @@ -238,7 +238,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.11.0" }, "orig_nbformat": 4, "vscode": { diff --git a/notebooks/Exploring_API_Functions.ipynb b/notebooks/Exploring_API_Functions.ipynb index 4cd3ca7..a3fbdc1 100644 --- a/notebooks/Exploring_API_Functions.ipynb +++ b/notebooks/Exploring_API_Functions.ipynb @@ -278,7 +278,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.11.0" }, "orig_nbformat": 4, "vscode": { diff --git a/notebooks/Exploring_API_Functions_Authentication.ipynb b/notebooks/Exploring_API_Functions_Authentication.ipynb index f100683..d12525b 100644 --- a/notebooks/Exploring_API_Functions_Authentication.ipynb +++ b/notebooks/Exploring_API_Functions_Authentication.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -322,7 +322,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/src/pyprediktormapclient/analytics_helper.py b/src/pyprediktormapclient/analytics_helper.py index f848594..24e1431 100644 --- a/src/pyprediktormapclient/analytics_helper.py +++ b/src/pyprediktormapclient/analytics_helper.py @@ -40,6 +40,8 @@ class AnalyticsHelper: * Input checks for nodeIds in variables that requires format int:int:string """ + ID_PATTERN = r"^\d+:\d+:\S+$" + @validate_call def __init__(self, input: List): self.dataframe = pd.DataFrame( @@ -124,8 +126,7 @@ def namespaces_as_list(self, list_of_dicts: List) -> List: @validate_call def split_id(self, id: Any) -> dict: - pattern = r"^\d+:\d+:\S+$" - if not re.match(pattern, id): + if not re.match(self.ID_PATTERN, id): raise ValueError("Invalid id format") id_split = id.split(":")