From bc4f5f955a36d1ae70906bdb3c0b032a5df1b41a Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Tue, 20 Apr 2021 11:54:52 -0700 Subject: [PATCH 01/46] Fix bacnet integration test; rename test_driver to test_driver_unit --- ...py => test_platform_driver_forward_cov.py} | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) rename services/core/PlatformDriverAgent/tests/{test_driver_bacnet_cov.py => test_platform_driver_forward_cov.py} (80%) diff --git a/services/core/PlatformDriverAgent/tests/test_driver_bacnet_cov.py b/services/core/PlatformDriverAgent/tests/test_platform_driver_forward_cov.py similarity index 80% rename from services/core/PlatformDriverAgent/tests/test_driver_bacnet_cov.py rename to services/core/PlatformDriverAgent/tests/test_platform_driver_forward_cov.py index 18cecec127..bf10c871b4 100644 --- a/services/core/PlatformDriverAgent/tests/test_driver_bacnet_cov.py +++ b/services/core/PlatformDriverAgent/tests/test_platform_driver_forward_cov.py @@ -35,15 +35,15 @@ # BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY # under Contract DE-AC05-76RL01830 # }}} - import logging +import os import pytest import gevent import gevent.subprocess as subprocess -from gevent.subprocess import Popen +from gevent.subprocess import check_call from mock import MagicMock from volttron.platform.agent import utils -from volttron.platform import get_services_core +from volttron.platform import get_services_core, get_volttron_root from volttron.platform.agent.known_identities import PLATFORM_DRIVER utils.setup_logging() @@ -51,7 +51,7 @@ @pytest.fixture(scope="module") -def test_agent(request, volttron_instance): +def test_agent(volttron_instance): """Dynamic agent for sending rpc calls and listening to the bus""" agent = volttron_instance.build_agent() agent.cov_callback = MagicMock(name="cov_callback") @@ -62,36 +62,33 @@ def test_agent(request, volttron_instance): prefix="devices/fakedriver/all", callback=agent.cov_callback).get() - def stop_agent(): - print("In teardown method of query_agent") - agent.core.stop() + yield agent - request.addfinalizer(stop_agent) - return agent + _log.info("In teardown method of query_agent") + agent.core.stop() @pytest.mark.driver -def test_cov_update_published(volttron_instance, test_agent): +def test_forward_bacnet_cov_value(volttron_instance, test_agent): """Tests the functionality of BACnet change of value forwarding in the Platform Driver and driver.py""" # Reset platform driver config store cmd = ['volttron-ctl', 'config', 'delete', PLATFORM_DRIVER, '--all'] - process = Popen(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - result = process.wait() - assert result == 0 + retcode = check_call(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert retcode == 0 # Add fake device configuration + fake_csv_infile = os.path.join(get_volttron_root(), 'examples/configurations/drivers/fake.csv') cmd = ['volttron-ctl', 'config', 'store', PLATFORM_DRIVER, - 'fake.csv', 'examples/configurations/drivers/fake.csv', '--csv'] - process = Popen(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - result = process.wait() - assert result == 0 + 'fake.csv', fake_csv_infile, '--csv'] + retcode = check_call(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert retcode == 0 + fakedriver_infile = os.path.join(get_volttron_root(), 'examples/configurations/drivers/fake.config') cmd = ['volttron-ctl', 'config', 'store', PLATFORM_DRIVER, - "devices/fakedriver", 'examples/configurations/drivers/fake.config', '--json'] - process = Popen(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - result = process.wait() - assert result == 0 + "devices/fakedriver", fakedriver_infile, '--json'] + retcode = check_call(cmd, env=volttron_instance.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert retcode == 0 # install platform driver, start the platform driver, which starts the device platform_uuid = volttron_instance.install_agent( From 2250b50845a19e797f4aa3d2fe380be38672a301 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Tue, 30 Jun 2020 13:08:59 -0400 Subject: [PATCH 02/46] Added missing return to volttron/platform/web.get_bearer() --- volttron/platform/web/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/volttron/platform/web/__init__.py b/volttron/platform/web/__init__.py index 099f606ef7..5c27d95d31 100644 --- a/volttron/platform/web/__init__.py +++ b/volttron/platform/web/__init__.py @@ -66,6 +66,8 @@ def get_bearer(env): auth_type, bearer = http_auth.split(' ') if auth_type.upper() != 'BEARER': raise NotAuthorized("Invalid HTTP_AUTHORIZATION header passed, must be Bearer") + else: + return bearer else: cookiestr = env.get('HTTP_COOKIE') if not cookiestr: From 979fc0acac3aaf8488beee79250dbbf8c05b50d5 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Tue, 30 Jun 2020 13:12:01 -0400 Subject: [PATCH 03/46] Removed unreachable code from volttron/platform/web/master_web_service.app_routing() --- volttron/platform/web/platform_web_service.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/volttron/platform/web/platform_web_service.py b/volttron/platform/web/platform_web_service.py index 055e2154c6..56019c9f13 100644 --- a/volttron/platform/web/platform_web_service.py +++ b/volttron/platform/web/platform_web_service.py @@ -491,11 +491,6 @@ def app_routing(self, env, start_response): if isinstance(retvalue, Response): return retvalue(env, start_response) - #return self.process_response(start_response, retvalue) - elif isinstance(retvalue, Response): # werkzueg Response - for d in retvalue(env, start_response): - print(d) - return retvalue(env, start_response) else: return retvalue[0] From 0488f26f0ff2b58157aee5e93f5ce8ecd899a383 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Fri, 23 Apr 2021 14:56:53 -0400 Subject: [PATCH 04/46] Fixed logic error in platform/web/__init__.get_user_claims_from_bearer(). --- volttron/platform/web/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volttron/platform/web/__init__.py b/volttron/platform/web/__init__.py index 5c27d95d31..dec0cb4bea 100644 --- a/volttron/platform/web/__init__.py +++ b/volttron/platform/web/__init__.py @@ -112,7 +112,7 @@ def __get_key_and_algorithm__(env, ssl_public_key): def get_user_claim_from_bearer(bearer, web_secret_key=None, tls_public_key=None): if web_secret_key is None and tls_public_key is None: raise ValueError("web_secret_key or tls_public_key must be set") - if web_secret_key is None and tls_public_key is None: + if web_secret_key is not None and tls_public_key is not None: raise ValueError("web_secret_key or tls_public_key must be set not both") if web_secret_key is not None: From 9072ad6714676a054870518a3b86a51892353a78 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Fri, 23 Apr 2021 15:39:47 -0400 Subject: [PATCH 05/46] Updated web authentication to allow time checks. Added concept of refresh & access tokens. --- volttron/platform/web/__init__.py | 9 +- .../platform/web/authenticate_endpoint.py | 101 ++++++++++++++++-- volttron/platform/web/platform_web_service.py | 20 ++-- 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/volttron/platform/web/__init__.py b/volttron/platform/web/__init__.py index dec0cb4bea..0f0d4eece9 100644 --- a/volttron/platform/web/__init__.py +++ b/volttron/platform/web/__init__.py @@ -39,6 +39,7 @@ from http.cookies import SimpleCookie import logging +from datetime import datetime from volttron.platform import get_platform_config try: @@ -123,7 +124,7 @@ def get_user_claim_from_bearer(bearer, web_secret_key=None, tls_public_key=None) pubkey = tls_public_key # if isinstance(tls_public_key, str): # pubkey = CertWrapper.load_cert(tls_public_key) - return jwt.decode(bearer, pubkey, algorithms=algorithm) - - - + claims = jwt.decode(bearer, pubkey, algorithms=algorithm) + exp = datetime.utcfromtimestamp(claims['exp']) + nbf = datetime.utcfromtimestamp(claims['nbf']) + return claims if not (exp < datetime.utcnow() <= nbf) else {} diff --git a/volttron/platform/web/authenticate_endpoint.py b/volttron/platform/web/authenticate_endpoint.py index 0af6ff425f..25b3afa56c 100644 --- a/volttron/platform/web/authenticate_endpoint.py +++ b/volttron/platform/web/authenticate_endpoint.py @@ -2,6 +2,8 @@ import os import re from urllib.parse import parse_qs +from datetime import datetime, timedelta +import json import jwt from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateNotFound @@ -32,9 +34,12 @@ class AuthenticateEndpoints(object): - def __init__(self, tls_private_key=None, web_secret_key=None): + def __init__(self, tls_private_key=None, tls_public_key=None, web_secret_key=None): + self.refresh_token_timeout = 240 # minutes before token expires. TODO: Should this be a setting somewhere? + self.access_token_timeout = 15 # minutes before token expires. TODO: Should this be a setting somewhere? self._tls_private_key = tls_private_key + self._tls_public_key = tls_public_key self._web_secret_key = web_secret_key if self._tls_private_key is None and self._web_secret_key is None: raise ValueError("Must have either ssl_private_key or web_secret_key specified!") @@ -72,21 +77,44 @@ def get_routes(self): :return: """ return [ - (re.compile('^/authenticate'), 'callable', self.get_auth_token) + (re.compile('^/authenticate'), 'callable', self.handle_authenticate) ] - def get_auth_token(self, env, data): + def handle_authenticate(self, env, data): """ - Creates an authentication token to be returned to the caller. The - response will be a text/plain encoded user + Callback for /authenticate endpoint. + Routes request based on HTTP method and returns a text/plain encoded token or error. + + :param env: + :param data: + :return: Response + """ + method = env.get('REQUEST_METHOD') + if method == 'POST': + response = self.get_auth_tokens(env, data) + elif method == 'PUT': + response = self.renew_auth_token(env, data) + elif method == 'DELETE': + response = self.revoke_auth_token(env, data) + else: + error = f"/authenticate endpoint accepts only POST, PUT, or DELETE methods. Received: {method}" + _log.warning(error) + return Response(error, status='405 Method Not Allowed', content_type='text/plain') + return response + + def get_auth_tokens(self, env, data): + """ + Creates an authentication refresh and acccss tokens to be returned to the caller. The + response will be a text/plain encoded user. Data should contain: + { + "username": "", + "password": "" + } :param env: :param data: :return: """ - if env.get('REQUEST_METHOD') != 'POST': - _log.warning("Authentication must use POST request.") - return Response('401 Unauthorized', status='401 Unauthorized', content_type='text/html') assert len(self._userdict) > 0, "No users in user dictionary, set the administrator password first!" @@ -118,12 +146,63 @@ def get_auth_token(self, env, data): if user is None: _log.error("No matching user for passed username: {}".format(username)) return Response('', status='401') - + access_token, refresh_token = self._get_tokens(user) + response = Response(json.dumps({"refresh_token": refresh_token, "access_token": access_token}), + content_type="application/json") + return response + + def _get_tokens(self, claims): + now = datetime.utcnow() + claims['iat'] = now + claims['nbf'] = now + claims['exp'] = now + timedelta(minutes=self.access_token_timeout) + claims['grant_type'] = 'access_token' algorithm = 'RS256' if self._tls_private_key is not None else 'HS256' encode_key = self._tls_private_key if algorithm == 'RS256' else self._web_secret_key - encoded = jwt.encode(user, encode_key, algorithm=algorithm) + access_token = jwt.encode(claims, encode_key, algorithm=algorithm) + claims['exp'] = now + timedelta(minutes=self.refresh_token_timeout) + claims['grant_type'] = 'refresh_token' + refresh_token = jwt.encode(claims, encode_key, algorithm=algorithm) + return access_token.decode('utf-8'), refresh_token.decode('utf8') - return Response(encoded, content_type="text/plain") + def renew_auth_token(self, env, data): + """ + Creates a new authentication access token to be returned to the caller. The + response will be a text/plain encoded user. Request should contain: + • Content Type: application/json + • Authorization: BEARER + • Body (optional): + { + "current_access_token": "" + } + + :param env: + :param data: + :return: + """ + current_access_token = data.get('current_access_token') + from volttron.platform.web import get_bearer, get_user_claim_from_bearer, NotAuthorized + try: + current_refresh_token = get_bearer(env) + claims = get_user_claim_from_bearer(current_refresh_token, web_secret_key=self._web_secret_key, + tls_public_key=self._tls_public_key) + except NotAuthorized: + _log.error("Unauthorized user attempted to connect to {}".format(env.get('PATH_INFO'))) + return Response('Unauthorized User', status="401 Unauthorized") + + if claims.get('grant_type') != 'refresh_token' or not claims.get('groups'): + return Response('Invalid refresh token.', status="401 Unauthorized") + else: + # TODO: Consider blacklisting and reissuing refresh tokens also when used. + new_access_token, _ = self._get_tokens(claims) + if current_access_token: + pass # TODO: keep current subscriptions? blacklist old token? + return Response(json.dumps({"access_token": new_access_token}), content_type="application/json") + + def revoke_auth_token(self, env, data): + # TODO: Blacklist old token? Immediately close websockets? + return Response('DELETE /authenticate is not yet implemented', status='501 Not Implemented', + content_type='text/plain') def __get_user(self, username, password): """ diff --git a/volttron/platform/web/platform_web_service.py b/volttron/platform/web/platform_web_service.py index 56019c9f13..04cea7716a 100644 --- a/volttron/platform/web/platform_web_service.py +++ b/volttron/platform/web/platform_web_service.py @@ -186,18 +186,20 @@ def remove_unconnnected_routes(self): def get_user_claims(self, bearer): from volttron.platform.web import get_user_claim_from_bearer if self.core.messagebus == 'rmq': - return get_user_claim_from_bearer(bearer, - tls_public_key=self._certs.get_cert_public_key( - get_fq_identity(self.core.identity))) - if self.web_ssl_cert is not None: - return get_user_claim_from_bearer(bearer, - tls_public_key=CertWrapper.get_cert_public_key(self.web_ssl_cert)) + claims = get_user_claim_from_bearer(bearer, + tls_public_key=self._certs.get_cert_public_key( + get_fq_identity(self.core.identity))) + elif self.web_ssl_cert is not None: + claims = get_user_claim_from_bearer(bearer, + tls_public_key=CertWrapper.get_cert_public_key(self.web_ssl_cert)) elif self._web_secret_key is not None: - return get_user_claim_from_bearer(bearer, web_secret_key=self._web_secret_key) + claims = get_user_claim_from_bearer(bearer, web_secret_key=self._web_secret_key) else: raise ValueError("Configuration error secret key or web ssl cert must be not None.") + return claims if claims.get('grant_type') == 'access_token' else {} + @RPC.export def websocket_send(self, endpoint, message): _log.debug("Sending data to {} with message {}".format(endpoint, @@ -798,9 +800,11 @@ def startupagent(self, sender, **kwargs): if parsed.scheme == 'https': if self.core.messagebus == 'rmq': ssl_private_key = self._certs.get_pk_bytes(get_fq_identity(self.core.identity)) + ssl_public_key = self._certs.get_cert_public_key(get_fq_identity(self.core.identity)) else: ssl_private_key = CertWrapper.get_private_key(ssl_key) - for rt in AuthenticateEndpoints(tls_private_key=ssl_private_key).get_routes(): + ssl_public_key = CertWrapper.get_cert_public_key(self.web_ssl_cert) + for rt in AuthenticateEndpoints(tls_private_key=ssl_private_key, tls_public_key=ssl_public_key).get_routes(): self.registeredroutes.append(rt) else: # We don't have a private ssl key if we aren't using ssl. From 1b0ce34582ac02da63ba7b510422da365a56dc1f Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Thu, 29 Apr 2021 16:06:51 -0700 Subject: [PATCH 06/46] Fix typos; formatting for PlatformDriver docs --- .../driver-framework/drivers-overview.rst | 26 ++++++++--------- .../platform-driver/platform-driver.rst | 28 +++++++++---------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/source/driver-framework/drivers-overview.rst b/docs/source/driver-framework/drivers-overview.rst index 074230e4ac..6b4a17304c 100644 --- a/docs/source/driver-framework/drivers-overview.rst +++ b/docs/source/driver-framework/drivers-overview.rst @@ -11,7 +11,7 @@ Platform Driver process. Driver instances are created by the Platform Driver when a new driver configuration is added to the configuration store. Drivers use the following topic pattern `devices///`. When a configuration file is added to the Platform Driver's store using this pattern, the Platform Driver creates a Driver Agent. The Driver agent is in turn -instantiated with a instance of the Interface class corresponding to the `driver_type` parameter in the configuration +instantiated with an instance of the Interface class corresponding to the `driver_type` parameter in the configuration file. The Interface class is responsible for implementing the communication paradigms of a device or protocol. Once configured, the Platform Driver periodically polls the Driver Agent for data which is collected from the interface class. Additionally, points can be requested ad-hoc via the Platform Driver's JSON-RPC method "get_point". Points may be set @@ -21,12 +21,10 @@ by using JSON-RPC with the Actuator agent to set up a schedule and calling the " Driver Conventions ****************** -- Drivers are polled by the Platform Driver agent and values can be set using the `Actuator Agent` -- Drivers should have a 1-to-1 relationship with a device -- Driver modules should be written in Python files in the `services/core/PlatformDriverAgent/platform_driver/interfaces` - directory in the VOLTTRON repository. The platform driver will search for a Python file in this directory matching the - name provided by the `driver_type` value from the driver configuration when creating the Driver agent. -- Driver code consists of an Interface class (exactly named), supported in most cases by one or more Register classes +* Drivers are polled by the Platform Driver agent and values can be set using the `Actuator Agent` +* Drivers should have a 1-to-1 relationship with a device +* Driver modules should be written in Python files in the `services/core/PlatformDriverAgent/platform_driver/interfaces` directory in the VOLTTRON repository. The platform driver will search for a Python file in this directory matching the name provided by the `driver_type` value from the driver configuration when creating the Driver agent. +* Driver code consists of an Interface class (exactly named), supported in most cases by one or more Register classes .. _Driver_Communication: @@ -51,18 +49,18 @@ The diagram features several entities that comprise the platform and its connect message bus is built around existing message bus software; currently VOLTTRON supports RabbitMQ and ZeroMQ. The VOLTTRON integration includes Pub/Sub and JSON RPC interfaces for agent and driver communication. * VOLTTRON Platform Agents and Subsystems - These agents and subsystems are installed on the platform to manage the - platform. They provide many user facing functions, aid in communication and manage other agents and drivers. -* User's Agents - These agents are either agents included in the core repository but installed by a user, or user built + platform. They provide many user facing functions, aid in communication, and manage other agents and drivers. +* User's Agents - These agents are either agents included in the core repository but installed by a user or user-built agent modules. They may perform a huge variety of user specified tasks, including data collection, device control, simulation, etc. * Platform Driver Agent - This agent is installed by a user to facilitate communication with drivers. Drivers should not communicated with directly - the platform driver implements several features for communicating with drivers to ensure smooth operation and consistent driver behavior. -* Actuator agent - This agent is installed by user to provide scheduling capability for controlling drivers. The +* Actuator agent - This agent is installed by a user to provide scheduling capability for controlling drivers. The Platform Driver does not include protections for race conditions, etc. It is always recommended to use the Actuator agent to set values on a device. * Device Driver - Drivers are special purpose agents which provide an interface between the platform driver and devices - such as Modbus, and BACnet devices. Drivers implement a specific set of features for protecting device communication + such as Modbus and BACnet devices. Drivers implement a specific set of features for protecting device communication, ensuring uniform behaviors across different devices. * Device - Devices may be low level physical computers for controlling various systems such as PLCs (Programmable Logic Controller), devices which communicate on the local network (such as a Smart T.V.), or devices which are accessed via @@ -76,9 +74,9 @@ Connectivity of the platform follows the following paradigm: * Platform agents (including the Platform Driver and Actuator), subsystems, and user agents communicate with the message bus via a publish/subscribe system. -* Agents can communicate "directly" to each other via JSONRPC calls - JSONRPC calls use the VOLTTRON message bus router +* Agents can communicate "directly" to each other via JSONRPC calls. JSONRPC calls use the VOLTTRON message bus router to "direct" messages to an intended recipient. RPC calls from an agent specify a function for the recipient to - perform including input parameters, and the response to the sender should contain the value output by the specified + perform including input parameters; the response to the sender should contain the value output by the specified function. * The Platform Driver will periodically poll device drivers. This functionality is intentionally not user-facing. The Platform Driver iterates over the configured drivers and calls their respective "scrape_all" methods. This will trigger @@ -151,7 +149,7 @@ Installing the Fake Driver ************************** The Fake Driver is included as a way to quickly see data published to the message bus in a format that mimics what a -true driver would produce. This is a simple implementation of the VOLTTRON driver framework. +real driver would produce. This is a simple implementation of the VOLTTRON driver framework. See :ref:`instructions for installing the fake driver ` diff --git a/docs/source/driver-framework/platform-driver/platform-driver.rst b/docs/source/driver-framework/platform-driver/platform-driver.rst index 716e2b3d1e..9a40b20854 100644 --- a/docs/source/driver-framework/platform-driver/platform-driver.rst +++ b/docs/source/driver-framework/platform-driver/platform-driver.rst @@ -88,7 +88,7 @@ The easiest way to install the requirements for drivers included in the VOLTTRON Platform Driver Configuration ============================= -The Platform Driver Agent configuration consists of general settings for all devices. The default values of the Master +The Platform Driver Agent configuration consists of general settings for all devices. The default values of the Platform Driver should be sufficient for most users. The user may optionally change the interval between device scrapes with the driver_scrape_interval. @@ -152,13 +152,13 @@ The following settings are required for all device configurations: - **driver_config** - Driver specific setting go here. See below for driver specific settings. - **driver_type** - Type of driver to use for this device: bacnet, modbus, fake, etc. - **registry_config** - Reference to a configuration file in the configuration store for registers - on the device. See the `Registry-Configuration-File`_ section below or - and the :ref:`Adding Device Configurations to the Configuration Store ` section in + on the device. See the `Registry-Configuration-File`_ section below and/or + the :ref:`Adding Device Configurations to the Configuration Store ` section in the driver framework docs. These settings are optional: - - **interval** - Period which to scrape the device and publish the results in seconds. Defaults to 60 seconds. + - **interval** - Period to scrape the device and publish the results in seconds. Defaults to 60 seconds. - **heart_beat_point** - A Point which to toggle to indicate a heartbeat to the device. A point with this ``Volttron Point Name`` must exist in the registry. If this setting is missing the driver will not send a heart beat signal to the device. Heart beats are triggered by the :ref:`Actuator Agent ` which must be running to @@ -167,7 +167,7 @@ These settings are optional: These settings are used to create the topic that this device will be referenced by following the VOLTTRON convention of ``{campus}/{building}/{unit}``. This will also be the topic published on, when the device is periodically scraped for -it's current state. +its current state. The topic used to reference the device is derived from the name of the device configuration in the store. See the :ref:`Adding Device Configurations to the Configuration Store ` section of the driver @@ -239,7 +239,7 @@ is accessible with the Actuator Agent via `PNNL/ISB1/vav1`. The name of a registry configuration must match the name used to refer to it in the driver configuration. The reference is not case sensitive. -If the Platform Driver Agent is running any changes to the configuration store will immediately affect the running devices +If the Platform Driver Agent is running, any changes to the configuration store will immediately affect the running devices according to the changes. Example @@ -279,7 +279,7 @@ Converting Old Style Configuration The new Platform Driver no longer supports the old style of device configuration. The old `device_list` setting is ignored. -To simplify updating to the new format `scripts/update_platform_driver_config.py` is provide to automatically update to +To simplify updating to the new format, `scripts/update_platform_driver_config.py` is provided to automatically update to the new configuration format. With the platform running run: @@ -288,12 +288,12 @@ With the platform running run: python scripts/update_platform_driver_config.py -old_configuration`` is the main configuration file in the old format. The script automatically modifies the driver +`old_configuration` is the main configuration file in the old format. The script automatically modifies the driver files to create references to CSV files and adds the CSV files with the appropriate name. `output` is the target output directory. -If the ``--keep-old`` switch is used the old configurations in the output directory (if any) will not be deleted before +If the ``--keep-old`` switch is used, the old configurations in the output directory (if any) will not be deleted before new configurations are created. Matching names will still be overwritten. The output from `scripts/update_platform_driver_config.py` can be automatically added to the configuration store @@ -307,7 +307,7 @@ the process of changing and updating a large number of configurations. See the ` Device Scalability Settings --------------------------- -In order to improve the scalability of the platform unneeded device state publishes for a device can be turned off. +To improve the scalability of the platform, unneeded device state publishes for a device can be turned off. All of the following setting are optional and will override the value set in the main platform driver configuration. - **publish_depth_first_all** - Enable "depth first" publish of all points to a single topic. @@ -339,8 +339,8 @@ Polling Once running, the Platform Driver will spawn drivers using the `driver_type` parameter of the :ref:`driver configuration ` and periodically poll devices for all point data specified in -the :ref:`registry configuration ` (at the interval specified by the interval parameter -of the driver configuration). +the :ref:`registry configuration ` at the interval specified by the interval parameter +of the driver configuration. By default, the value of each register on a device is published 4 different ways when the device state is published. Consider the following settings in a driver configuration stored under the name ``devices/pnnl/isb1/vav1``: @@ -355,14 +355,14 @@ Consider the following settings in a driver configuration stored under the name "registry_config":"config://registry_configs/vav.csv", } -In the `vav.csv` file is a register with the name `temperature`. For these examples the current value of the +In the `vav.csv` file, a register has the name `temperature`. For these examples the current value of the register on the device happens to be 75.2 and the meta data is .. code-block:: json {"units": "F"} -When the driver publishes the device state the following 2 things will be published for this register: +When the driver publishes the device state the following two things will be published for this register: A "depth first" publish to the topic `devices/pnnl/isb1/vav1/temperature` with the following message: From 1e0d7fdbc2976db740366754a97313efe5046dae Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Fri, 30 Apr 2021 16:17:46 -0700 Subject: [PATCH 07/46] Address comments; add example on RPC call --- .../driver-framework/drivers-overview.rst | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/driver-framework/drivers-overview.rst b/docs/source/driver-framework/drivers-overview.rst index 6b4a17304c..d1ce68ae1f 100644 --- a/docs/source/driver-framework/drivers-overview.rst +++ b/docs/source/driver-framework/drivers-overview.rst @@ -10,7 +10,7 @@ Platform Driver process. Driver instances are created by the Platform Driver when a new driver configuration is added to the configuration store. Drivers use the following topic pattern `devices///`. When a configuration file is added -to the Platform Driver's store using this pattern, the Platform Driver creates a Driver Agent. The Driver agent is in turn +to the Platform Driver's store using this pattern, the Platform Driver creates a Driver Agent. The Driver agent is then instantiated with an instance of the Interface class corresponding to the `driver_type` parameter in the configuration file. The Interface class is responsible for implementing the communication paradigms of a device or protocol. Once configured, the Platform Driver periodically polls the Driver Agent for data which is collected from the interface class. @@ -21,10 +21,10 @@ by using JSON-RPC with the Actuator agent to set up a schedule and calling the " Driver Conventions ****************** -* Drivers are polled by the Platform Driver agent and values can be set using the `Actuator Agent` -* Drivers should have a 1-to-1 relationship with a device +* Drivers are polled by the Platform Driver agent and values can be set using the `Actuator Agent`. +* Drivers should have a 1-to-1 relationship with a device. * Driver modules should be written in Python files in the `services/core/PlatformDriverAgent/platform_driver/interfaces` directory in the VOLTTRON repository. The platform driver will search for a Python file in this directory matching the name provided by the `driver_type` value from the driver configuration when creating the Driver agent. -* Driver code consists of an Interface class (exactly named), supported in most cases by one or more Register classes +* Driver code consists of an Interface class (exactly named), supported in most cases by one or more Register classes. .. _Driver_Communication: @@ -50,18 +50,18 @@ The diagram features several entities that comprise the platform and its connect VOLTTRON integration includes Pub/Sub and JSON RPC interfaces for agent and driver communication. * VOLTTRON Platform Agents and Subsystems - These agents and subsystems are installed on the platform to manage the platform. They provide many user facing functions, aid in communication, and manage other agents and drivers. -* User's Agents - These agents are either agents included in the core repository but installed by a user or user-built +* User's Agents - These agents are either agents included in the core repository but installed by a user or built by an end-user's agent modules. They may perform a huge variety of user specified tasks, including data collection, device control, simulation, etc. -* Platform Driver Agent - This agent is installed by a user to facilitate communication with drivers. Drivers should not - communicated with directly - the platform driver implements several features for communicating with drivers to ensure +* Platform Driver Agent - This agent facilitates communication with drivers. Agents should not + communicate directly with drivers. The platform driver implements several features for communicating with drivers to ensure smooth operation and consistent driver behavior. -* Actuator agent - This agent is installed by a user to provide scheduling capability for controlling drivers. The +* Actuator agent - This agent provides scheduling capability for controlling drivers. The Platform Driver does not include protections for race conditions, etc. It is always recommended to use the Actuator agent to set values on a device. * Device Driver - Drivers are special purpose agents which provide an interface between the platform driver and devices - such as Modbus and BACnet devices. Drivers implement a specific set of features for protecting device communication, - ensuring uniform behaviors across different devices. + such as Modbus and BACnet devices. Drivers implement a specific set of features for protecting device communication and + ensure uniform behaviors across different devices. * Device - Devices may be low level physical computers for controlling various systems such as PLCs (Programmable Logic Controller), devices which communicate on the local network (such as a Smart T.V.), or devices which are accessed via a remote web API (other smart devices). @@ -74,10 +74,10 @@ Connectivity of the platform follows the following paradigm: * Platform agents (including the Platform Driver and Actuator), subsystems, and user agents communicate with the message bus via a publish/subscribe system. -* Agents can communicate "directly" to each other via JSONRPC calls. JSONRPC calls use the VOLTTRON message bus router - to "direct" messages to an intended recipient. RPC calls from an agent specify a function for the recipient to - perform including input parameters; the response to the sender should contain the value output by the specified - function. +* Agents can communicate "directly" to each other via JSONRPC (RPC). A JSONRPC call uses the VOLTTRON message bus router + to "direct" messages to an intended recipient. For example, suppose Agent B has a function called `do_task(x)` which + takes an `x` as an input parameter and returns an output. Using an RPC call, Agent A can specify the input parameter and + call Agent B's `do_task(x)` function. Agent B would receive the call, execute the task, and return the required output to Agent A. * The Platform Driver will periodically poll device drivers. This functionality is intentionally not user-facing. The Platform Driver iterates over the configured drivers and calls their respective "scrape_all" methods. This will trigger the drivers to collect point values. From 47c231c1e9b4d6eaf90e97f48a3857309336acb4 Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Wed, 5 May 2021 09:47:18 -0700 Subject: [PATCH 08/46] Put back original RPC description --- docs/source/driver-framework/drivers-overview.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/driver-framework/drivers-overview.rst b/docs/source/driver-framework/drivers-overview.rst index d1ce68ae1f..9ef2b5f240 100644 --- a/docs/source/driver-framework/drivers-overview.rst +++ b/docs/source/driver-framework/drivers-overview.rst @@ -75,9 +75,9 @@ Connectivity of the platform follows the following paradigm: * Platform agents (including the Platform Driver and Actuator), subsystems, and user agents communicate with the message bus via a publish/subscribe system. * Agents can communicate "directly" to each other via JSONRPC (RPC). A JSONRPC call uses the VOLTTRON message bus router - to "direct" messages to an intended recipient. For example, suppose Agent B has a function called `do_task(x)` which - takes an `x` as an input parameter and returns an output. Using an RPC call, Agent A can specify the input parameter and - call Agent B's `do_task(x)` function. Agent B would receive the call, execute the task, and return the required output to Agent A. + to "direct" messages to an intended recipient. RPC calls from an agent specify a function for the recipient to + perform including input parameters; the response to the sender should contain the value output by the specified + function. * The Platform Driver will periodically poll device drivers. This functionality is intentionally not user-facing. The Platform Driver iterates over the configured drivers and calls their respective "scrape_all" methods. This will trigger the drivers to collect point values. From 3ec8318d1e08f74b8640337c8f872df2610f1e6b Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Wed, 5 May 2021 11:01:01 -0700 Subject: [PATCH 09/46] Update SchedulerExample agent and Actuator README docs --- .../schedule_example/agent.py | 45 +++++++++++-------- services/core/ActuatorAgent/README.md | 3 ++ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/examples/SchedulerExample/schedule_example/agent.py b/examples/SchedulerExample/schedule_example/agent.py index 6ae69a9250..3371418261 100644 --- a/examples/SchedulerExample/schedule_example/agent.py +++ b/examples/SchedulerExample/schedule_example/agent.py @@ -72,7 +72,7 @@ def schedule_example(config_path, **kwargs): class SchedulerExample(Agent): '''This agent can be used to demonstrate scheduling and - acutation of devices. It reserves a non-existant device, then + acutation of devices. It reserves a non-existent device, then acts when its time comes up. Since there is no device, this will cause an error. ''' @@ -126,18 +126,16 @@ def publish_schedule(self): msg = [ - ['campus/building/unit',start,end] - #Could add more devices - # ["campus/building/device1", #First time slot. - # "2014-1-31 12:27:00", #Start of time slot. - # "2016-1-31 12:29:00"], #End of time slot. - # ["campus/building/device2", #Second time slot. - # "2014-1-31 12:26:00", #Start of time slot. - # "2016-1-31 12:30:00"], #End of time slot. - # ["campus/building/device3", #Third time slot. - # "2014-1-31 12:30:00", #Start of time slot. - # "2016-1-31 12:32:00"], #End of time slot. - #etc... + ['campus/building/unit', start, end], + ["campus/building/device1", #First time slot. + "2014-1-31 12:27:00", #Start of time slot. + "2016-1-31 12:29:00"], #End of time slot. + ["campus/building/device2", #Second time slot. + "2014-1-31 12:26:00", #Start of time slot. + "2016-1-31 12:30:00"], #End of time slot. + ["campus/building/device3", #Third time slot. + "2014-1-31 12:30:00", #Start of time slot. + "2016-1-31 12:32:00"], #End of time slot. ] self.vip.pubsub.publish( 'pubsub', topics.ACTUATOR_SCHEDULE_REQUEST, headers, msg) @@ -160,7 +158,7 @@ def use_rpc(self): msg).get(timeout=10) print("schedule result", result) except Exception as e: - print ("Could not contact actuator. Is it running?") + print("Could not contact actuator. Is it running?") print(e) return @@ -173,13 +171,22 @@ def use_rpc(self): 'campus/building/unit3/some_point', '0.0').get(timeout=10) print("Set result", result) + + topic_values = [] + for x in msg: + topic = x[0] + value = '0.0' + topic_values.append((topic, value)) + result = self.vip.rpc.call( + 'platform.actuator', + 'set_multiple_points', + self.core.identity, + topic_values).get(timeout=10) + print("Set_multiple_points result", result) except Exception as e: - print ("Expected to fail since there is no real device to set") + print("Expected to fail since there is no real device to set") print(e) - - - - + Agent.__name__ = 'ScheduleExampleAgent' return SchedulerExample(**kwargs) diff --git a/services/core/ActuatorAgent/README.md b/services/core/ActuatorAgent/README.md index 1b63be628b..75c4466274 100644 --- a/services/core/ActuatorAgent/README.md +++ b/services/core/ActuatorAgent/README.md @@ -5,6 +5,9 @@ devices. Agents may interact with the ActuatorAgent via either PUB/SUB or RPC, but it is recommended agents use RPC to interact with the ActuatorAgent. +For an example of an agent using RPC to interact with the ActuatorAgent, see the +`SchedulerExample agent `__ +from the Volttron repository. The PUB/SUB interface remains primarily for VOLTTRON 2.0 agents. From 9aa5193467d9a9c561d5ec014e2973af989dc0f1 Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Tue, 11 May 2021 16:53:52 -0700 Subject: [PATCH 10/46] Remove redundant actuator unit tests; cleanup actuator rpc tests --- .../tests/actuator-no-lock.config | 2 +- .../ActuatorAgent/tests/actuator_fixtures.py | 92 -- .../ActuatorAgent/tests/test_actuator_rpc.py | 838 +++++++----------- .../tests/test_actuator_rpc_unit.py | 376 -------- 4 files changed, 329 insertions(+), 979 deletions(-) delete mode 100644 services/core/ActuatorAgent/tests/actuator_fixtures.py delete mode 100644 services/core/ActuatorAgent/tests/test_actuator_rpc_unit.py diff --git a/services/core/ActuatorAgent/tests/actuator-no-lock.config b/services/core/ActuatorAgent/tests/actuator-no-lock.config index eade47ac61..6fe85e4a25 100644 --- a/services/core/ActuatorAgent/tests/actuator-no-lock.config +++ b/services/core/ActuatorAgent/tests/actuator-no-lock.config @@ -1,5 +1,5 @@ { "schedule_publish_interval": 10, "schedule_state_file": "actuator_state.test", - "allow_no_lock_write": true + "allow_no_lock_write": true # default is True } diff --git a/services/core/ActuatorAgent/tests/actuator_fixtures.py b/services/core/ActuatorAgent/tests/actuator_fixtures.py deleted file mode 100644 index c21834023e..0000000000 --- a/services/core/ActuatorAgent/tests/actuator_fixtures.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- {{{ -# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: -# -# Copyright 2020, Battelle Memorial Institute. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# This material was prepared as an account of work sponsored by an agency of -# the United States Government. Neither the United States Government nor the -# United States Department of Energy, nor Battelle, nor any of their -# employees, nor any jurisdiction or organization that has cooperated in the -# development of these materials, makes any warranty, express or -# implied, or assumes any legal liability or responsibility for the accuracy, -# completeness, or usefulness or any information, apparatus, product, -# software, or process disclosed, or represents that its use would not infringe -# privately owned rights. Reference herein to any specific commercial product, -# process, or service by trade name, trademark, manufacturer, or otherwise -# does not necessarily constitute or imply its endorsement, recommendation, or -# favoring by the United States Government or any agency thereof, or -# Battelle Memorial Institute. The views and opinions of authors expressed -# herein do not necessarily state or reflect those of the -# United States Government or any agency thereof. -# -# PACIFIC NORTHWEST NATIONAL LABORATORY operated by -# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY -# under Contract DE-AC05-76RL01830 -# }}} - -import contextlib - -from mock import create_autospec - -from services.core.ActuatorAgent.actuator.agent import ActuatorAgent, ScheduleManager -from services.core.ActuatorAgent.actuator.scheduler import RequestResult - - -class MockedAsyncResult: - """ - This class is used to help mock Responses from the vip subsystem - """ - def __init__(self, result): - self.result = result - - def get(self): - return self.result - - -@contextlib.contextmanager -def get_actuator_agent(vip_identity: str = "fake_vip_identity", - vip_rpc_call_res: MockedAsyncResult = MockedAsyncResult("fake_result"), - vip_message_peer: str = None, - device_state: dict = {}, - slot_requests_res: RequestResult = RequestResult(True, {("agent-id-1", "task-id-1")}, ""), - cancel_schedule_result: RequestResult = None): - """ - Creates an Actuator agent and mocks all required dependencies for unit testing - :param vip_identity: the identity of the Agent's Subsystem - :param vip_rpc_call_res: the response returned when calling a method of the Agent's Subsystem - :param vip_message_peer: the identity of the Agent's VIP, which is used internally - :param device_state: a mapping between a path and a DeviceState; this is an protected field of the Agent - :param slot_requests_res: the response returned when calling request_slots method of Agent's Schedule Manager - :param cancel_schedule_result: the response retunred when callin cancel_task method of Agent's Schedule Manaager - :return: - """ - ActuatorAgent.core.identity = "fake_core_identity" - actuator_agent = ActuatorAgent() - if vip_identity is not None: - actuator_agent.driver_vip_identity = vip_identity - actuator_agent.vip.rpc.call.return_value = vip_rpc_call_res - actuator_agent.vip.rpc.context.vip_message.peer = vip_message_peer - actuator_agent._device_states = device_state - actuator_agent._schedule_manager = create_autospec(ScheduleManager) - actuator_agent._schedule_manager.request_slots.return_value = slot_requests_res - actuator_agent._schedule_manager.get_next_event_time.return_value = None - actuator_agent._schedule_manager.cancel_task.return_value = cancel_schedule_result - actuator_agent._schedule_manager.get_schedule_state.return_value = {} - actuator_agent.core.schedule.return_value = None - - try: - yield actuator_agent - finally: - actuator_agent.vip.reset_mock() diff --git a/services/core/ActuatorAgent/tests/test_actuator_rpc.py b/services/core/ActuatorAgent/tests/test_actuator_rpc.py index 245c49ceab..63d997c588 100644 --- a/services/core/ActuatorAgent/tests/test_actuator_rpc.py +++ b/services/core/ActuatorAgent/tests/test_actuator_rpc.py @@ -37,7 +37,7 @@ # }}} """ -Pytest test cases for testing actuator agent using rpc calls. +Pytest integration test cases for testing actuator agent using rpc calls. """ import json @@ -200,8 +200,14 @@ def cleanup(): return cleanup_parameters +@pytest.mark.parametrize("taskid, expected_result, expected_info", [ + ('task_schedule_success', SUCCESS, ''), + (1234, FAILURE, 'MALFORMED_REQUEST: TypeError: taskid must be a nonempty string'), + ('', FAILURE, 'MALFORMED_REQUEST: TypeError: taskid must be a nonempty string'), + (None, FAILURE, 'MISSING_TASK_ID') +]) @pytest.mark.actuator -def test_schedule_success(publish_agent, cancel_schedules): +def test_request_new_schedule(publish_agent, cancel_schedules, taskid, expected_result, expected_info): """ Test responses for successful schedule request :param publish_agent: fixture invoked to setup all agents necessary and @@ -212,7 +218,6 @@ def test_schedule_success(publish_agent, cancel_schedules): print ("\n**** test_schedule_success ****") # used by cancel_schedules agentid = TEST_AGENT - taskid = 'task_schedule_success' cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) start = str(datetime.now()) @@ -229,109 +234,25 @@ def test_schedule_success(publish_agent, cancel_schedules): msg).get(timeout=10) # expected result {'info': u'', 'data': {}, 'result': SUCCESS} print(result) - assert result['result'] == SUCCESS - - -@pytest.mark.actuator -def test_schedule_error_int_taskid(publish_agent): - """ - Test responses for successful schedule request with integer task id - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - """ - print("\n**** test_schedule_error_int_taskid ****") - agentid = TEST_AGENT - taskid = 1234 - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=1)) - msg = [ - ['fakedriver1', start, end] - ] - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == FAILURE - assert result['info'] == 'MALFORMED_REQUEST: TypeError: taskid must be a nonempty string' - - -@pytest.mark.actuator -def test_schedule_empty_taskid(publish_agent, cancel_schedules): - """ - Test responses for successful schedule request when task id is an empty - string - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param cancel_schedules: fixture used to cancel the schedule at the end - of test so that other tests can use the same device and time slot - """ - print("\n**** test_schedule_empty_taskid ****") - # used by cancel_schedules - agentid = TEST_AGENT - taskid = '' - cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=1)) - msg = [ - ['fakedriver1', start, end] - ] - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == FAILURE - assert result['info'] == 'MALFORMED_REQUEST: TypeError: taskid must be a nonempty string' - - -@pytest.mark.actuator -def test_schedule_error_none_taskid(publish_agent): - """ - Test error responses for schedule request with taskid = None - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - """ - print("\n**** test_schedule_error_none_taskid ****") - agentid = TEST_AGENT - taskid = None - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=1)) - msg = [ - ['fakedriver1', start, end] - ] - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - print(result) - assert result['result'] == FAILURE - assert result['info'] == 'MISSING_TASK_ID' + assert result['result'] == expected_result + if not result['info']: + assert result['info'] == expected_info +@pytest.mark.parametrize("invalid_priority, expected_info", [ + ('LOW2', 'INVALID_PRIORITY'), + (None, 'MISSING_PRIORITY') +]) @pytest.mark.actuator -def test_schedule_error_invalid_priority(publish_agent): +def test_request_new_schedule_should_return_failure_on_bad_priority(publish_agent, invalid_priority, expected_info): """ Test error responses for schedule request with an invalid priority :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing """ print("\n**** test_schedule_error_invalid_priority ****") - taskid = 'task_invalid_priority' + taskid = 'task_bad_priority' start = str(datetime.now()) end = str(datetime.now() + timedelta(seconds=1)) msg = [ @@ -343,16 +264,17 @@ def test_schedule_error_invalid_priority(publish_agent): REQUEST_NEW_SCHEDULE, TEST_AGENT, taskid, - 'LOW2', + # 'LOW2', + invalid_priority, msg).get(timeout=10) # expected result {'info': u'', 'data': {}, 'result': SUCCESS} print(result) assert result['result'] == FAILURE - assert result['info'] == 'INVALID_PRIORITY' + assert result['info'] == expected_info @pytest.mark.actuator -def test_schedule_error_empty_message(publish_agent): +def test_request_new_schedule_should_return_failure_on_empty_message(publish_agent): """ Test error responses for schedule request with an empty message :param publish_agent: fixture invoked to setup all agents necessary and @@ -361,9 +283,7 @@ def test_schedule_error_empty_message(publish_agent): print("\n**** test_schedule_error_empty_message ****") taskid = 'task_empty_message' - msg = [ - - ] + msg = [] result = publish_agent.vip.rpc.call( PLATFORM_ACTUATOR, REQUEST_NEW_SCHEDULE, @@ -378,7 +298,7 @@ def test_schedule_error_empty_message(publish_agent): @pytest.mark.actuator -def test_schedule_error_duplicate_task(publish_agent, cancel_schedules): +def test_request_new_schedule_should_return_failure_on_duplicate_taskid(publish_agent, cancel_schedules): """ Test error responses for schedule request with task id that is already in use @@ -407,6 +327,7 @@ def test_schedule_error_duplicate_task(publish_agent, cancel_schedules): PRIORITY_LOW, msg).get(timeout=10) assert result['result'] == SUCCESS + # new request with same task id result = publish_agent.vip.rpc.call( PLATFORM_ACTUATOR, @@ -422,36 +343,7 @@ def test_schedule_error_duplicate_task(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_schedule_error_none_priority(publish_agent): - """ - Test error responses for schedule request with priority = None - :param publish_agent: fixture invoked to setup all agents necessary - and returns an instance of Agent object used for publishing - """ - print("\n**** test_schedule_error_none_priority ****") - taskid = 'task_none_priority' - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=1)) - msg = [ - ['fakedriver0', start, end] - ] - - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - TEST_AGENT, - taskid, - None, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == FAILURE - assert result['info'] == 'MISSING_PRIORITY' - - -@pytest.mark.actuator -def test_schedule_error_malformed_request(publish_agent): +def test_reques_new_schedule_error_malformed_request(publish_agent): """ Test error responses for schedule request with malformed request - request with only a device name and start time and no stop time @@ -480,7 +372,7 @@ def test_schedule_error_malformed_request(publish_agent): @pytest.mark.actuator -def test_schedule_preempt_self(publish_agent, cancel_schedules): +def test_request_new_schedule_should_succeed_on_preempt_self(publish_agent, cancel_schedules): """ Test error response for schedule request through pubsub. Test schedule preemption by a higher priority task from the same agent. @@ -557,7 +449,7 @@ def test_schedule_preempt_self(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_schedule_preempt_active_task(publish_agent, cancel_schedules): +def test_request_new_schedule_should_suceed_on_preempt_active_task(publish_agent, cancel_schedules): """ Test error response for schedule request. Test schedule preemption of a actively running task with priority @@ -638,7 +530,7 @@ def test_schedule_preempt_active_task(publish_agent, cancel_schedules): # This test checks to see if a requestid is no longer valid. # Since request ids are always vip identities and only one agent # is scheduling devices the expected lock error is not raised. -def test_schedule_preempt_active_task_gracetime(publish_agent, cancel_schedules): +def test_request_new_schedule_preempt_active_task_gracetime(publish_agent, cancel_schedules): """ Test error response for schedule request. Test schedule preemption of a actively running task with priority LOW by @@ -656,7 +548,7 @@ def test_schedule_preempt_active_task_gracetime(publish_agent, cancel_schedules) agentid = 'new_agent' taskid = 'task_high_priority3' cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) - # add low prority task as well since it won't get cancelled till end of grace time + # add low priority task as well since it won't get cancelled till end of grace time cancel_schedules.append( {'agentid': TEST_AGENT, 'taskid': 'task_low_priority3'}) @@ -752,7 +644,7 @@ def test_schedule_preempt_active_task_gracetime(publish_agent, cancel_schedules) @pytest.mark.actuator -def test_schedule_preempt_error_active_task(publish_agent, cancel_schedules): +def test_request_new_schedule_should_return_failure_on_preempt_active_task(publish_agent, cancel_schedules): """ Test error response for schedule request. Test schedule preemption of a actively running task with priority LOW by @@ -810,7 +702,7 @@ def test_schedule_preempt_error_active_task(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_schedule_preempt_future_task(publish_agent, cancel_schedules): +def test_request_new_schedule_should_succeed_on_preempt_future_task(publish_agent, cancel_schedules): """ Test error response for schedule request. Test schedule preemption of a future task with priority LOW by a higher @@ -889,7 +781,7 @@ def test_schedule_preempt_future_task(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_schedule_conflict_self(publish_agent): +def test_request_new_schedule_should_return_failure_on_conflicting_time_slots(publish_agent): """ Test error response for schedule request. Test schedule with conflicting time slots in the same request @@ -921,16 +813,15 @@ def test_schedule_conflict_self(publish_agent): @pytest.mark.actuator -def test_schedule_conflict(publish_agent, cancel_schedules): +def test_request_new_schedule_should_return_failure_on_conflicting_schedules(publish_agent, cancel_schedules): """ - Test schedule conflict with existing schdeule + Test schedule conflict with existing schedule :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing :param cancel_schedules: fixture used to cancel the schedule at the end of test so that other tests can use the same device and time slot """ - print ("\n**** test_schedule_conflict ****") # set agentid and task id for cancel_schedules fixture agentid = TEST_AGENT taskid = 'task_conflict1' @@ -966,7 +857,7 @@ def test_schedule_conflict(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_schedule_overlap_success(publish_agent, cancel_schedules): +def test_request_new_schedule_should_succeed_on_overlap_time_slots(publish_agent, cancel_schedules): """ Test schedule where stop time of one requested time slot is the same as start time of another requested time slot. @@ -1004,29 +895,7 @@ def test_schedule_overlap_success(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_cancel_error_invalid_taskid(publish_agent): - """ - Test error responses for schedule request. Test invalid task id - - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance - of Agent object used for publishing - """ - print ("\n**** test_cancel_error_invalid_taskid ****") - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_CANCEL_SCHEDULE, - TEST_AGENT, - 'invalid_cancel', - ).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == FAILURE - assert result['info'] == 'TASK_ID_DOES_NOT_EXIST' - - -@pytest.mark.actuator -def test_cancel_success(publish_agent): +def test_request_cancel_schedule_should_succeed(publish_agent): """ Test successful schedule cancel @@ -1063,78 +932,67 @@ def test_cancel_success(publish_agent): @pytest.mark.actuator -def test_get_default(publish_agent): +def test_request_cancel_schedule_should_return_failure_on_invalid_taskid(publish_agent): """ - Test get default value of a point + Test error responses for schedule request. Test invalid task id :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing + returns an instance + of Agent object used for publishing """ - print ("\n**** test_get_default ****") - + print ("\n**** test_cancel_error_invalid_taskid ****") result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, # Target agent - 'get_point', # Method - 'fakedriver1/SampleWritableFloat1' # point + PLATFORM_ACTUATOR, + REQUEST_CANCEL_SCHEDULE, + TEST_AGENT, + 'invalid_cancel', ).get(timeout=10) + # expected result {'info': u'', 'data': {}, 'result': SUCCESS} print(result) - assert result == 10.0 + assert result['result'] == FAILURE + assert result['info'] == 'TASK_ID_DOES_NOT_EXIST' +# We need to test the getters first before proceeding to testing the other actuator methods because +# some methods mutate driver points AND all tests share the same publish_agent setup @pytest.mark.actuator -def test_get_success(publish_agent, cancel_schedules): +def test_get_point_should_succeed(publish_agent): """ - Test getting a float value of a point through pubsub - Expected Result - value of the point + Test get default value of a point :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing - :param cancel_schedules: fixture used to cancel the schedule at - the end of test so that other tests can use the same device and time slot """ - print("\n**** test_get_value_success ****") - agentid = TEST_AGENT - taskid = 'task_set_and_get' - cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=2)) - msg = [ - ['fakedriver1', start, end] - ] - - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == SUCCESS - - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver1/SampleWritableFloat1', # Point to set - 1.0 # New value - ).get(timeout=10) - assert result == 1.0 + print ("\n**** test_get_default ****") result = publish_agent.vip.rpc.call( PLATFORM_ACTUATOR, # Target agent 'get_point', # Method 'fakedriver1/SampleWritableFloat1' # point ).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} print(result) - assert result == 1.0 + assert result == 10.0 +@pytest.mark.parametrize("topics", [ + (['fakedriver0/SampleWritableFloat1', + 'fakedriver1/SampleWritableFloat1']), + ([['fakedriver0', 'SampleWritableFloat1'], + ['fakedriver1', 'SampleWritableFloat1']]) +]) @pytest.mark.actuator -def test_get_success_with_point(publish_agent, cancel_schedules): +def test_get_multiple_points_should_succeed(publish_agent, cancel_schedules, topics): + results, errors = publish_agent.vip.rpc.call( + 'platform.actuator', + 'get_multiple_points', + topics).get(timeout=10) + + assert results == {'fakedriver0/SampleWritableFloat1': 10.0, 'fakedriver1/SampleWritableFloat1': 10.0} + assert errors == {} + + +@pytest.mark.actuator +def test_set_point_then_get_point_should_succeed(publish_agent, cancel_schedules): """ Test getting a float value of a point through RPC Expected Result - value of the point @@ -1186,14 +1044,13 @@ def test_get_success_with_point(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_get_error_invalid_point(publish_agent): +def test_get_point_raises_remote_error_on_invalid_point(publish_agent): """ Test getting a float value of a point through RPC with invalid point :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing """ - print ("\n**** test_get_error_invalid_point ****") try: result = publish_agent.vip.rpc.call( PLATFORM_ACTUATOR, # Target agent @@ -1206,54 +1063,10 @@ def test_get_error_invalid_point(publish_agent): @pytest.mark.actuator -def test_set_value_float(publish_agent, cancel_schedules, revert_devices): - """ - Test setting a float value of a point through rpc - Expected result = value of the actuation point - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param cancel_schedules: fixture used to cancel the schedule at the end - of test so that other tests can use the same device and time slot - :param revert_devices: list of devices to revert during test +def test_revert_point_should_succeed(publish_agent, cancel_schedules): """ - print("\n**** test_set_float_value ****") - taskid = 'task_set_float_value' - agentid = TEST_AGENT - device = 'fakedriver0' - cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) - revert_devices.append({'agentid': agentid, 'device': device}) - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=2)) - msg = [ - [device, start, end] - ] - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == SUCCESS - - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver0/SampleWritableFloat1', # Point to set - 2.5 # New value - ).get(timeout=10) - assert result == 2.5 - + Test reverting a float value of a point through rpc using only the topic parameter -@pytest.mark.actuator -def test_revert_point(publish_agent, cancel_schedules): - """ - Test setting a float value of a point through rpc - Expected result = value of the actuation point :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing :param cancel_schedules: fixture used to cancel the schedule at the end @@ -1314,10 +1127,9 @@ def test_revert_point(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_revert_point_with_point(publish_agent, cancel_schedules): +def test_revert_point_with_point_should_succeed(publish_agent, cancel_schedules): """ - Test setting a float value of a point through rpc - Expected result = value of the actuation point + Test reverting a float value of a point through rpc using both topic and point parameters :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing @@ -1379,17 +1191,17 @@ def test_revert_point_with_point(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_revert_device(publish_agent, cancel_schedules): +def test_revert_device_should_succeed(publish_agent, cancel_schedules): """ - Test setting a float value of a point through rpc - Expected result = value of the actuation point + Tests whether a point is set to its initial value upon calling revert_device. + Consequently, this tests requires a lot of setup, namely setting a point to a new value, + verifying the change, then calling revert_device and again verifying that the point is set to its original value. :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing :param cancel_schedules: fixture used to cancel the schedule at the end of test so that other tests can use the same device and time slot """ - print ("\n**** test_set_float_value ****") taskid = 'test_revert_point' agentid = TEST_AGENT cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) @@ -1444,26 +1256,27 @@ def test_revert_device(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_set_error_array(publish_agent, cancel_schedules): +def test_set_point_should_succeed(publish_agent, cancel_schedules, revert_devices): """ - Test setting a array of single float value of a point. Should return - type error - + Test setting a float value of a point through rpc + Expected result = value of the actuation point :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing :param cancel_schedules: fixture used to cancel the schedule at the end of test so that other tests can use the same device and time slot + :param revert_devices: list of devices to revert during test """ - print("\n**** test_set_error_array ****") - # set agentid and task id for cancel_schedules fixture + print("\n**** test_set_float_value ****") + taskid = 'task_set_float_value' agentid = TEST_AGENT - taskid = 'task_set_float_array_value' + device = 'fakedriver0' cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) + revert_devices.append({'agentid': agentid, 'device': device}) start = str(datetime.now()) end = str(datetime.now() + timedelta(seconds=2)) msg = [ - ['fakedriver0', start, end] + [device, start, end] ] result = publish_agent.vip.rpc.call( PLATFORM_ACTUATOR, @@ -1475,22 +1288,64 @@ def test_set_error_array(publish_agent, cancel_schedules): # expected result {'info': u'', 'data': {}, 'result': SUCCESS} print(result) assert result['result'] == SUCCESS - try: - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver0/SampleWritableFloat1', # Point to set - [2.5] # New value - ).get(timeout=10) - pytest.fail('Expecting RemoteError for trying to set array on point ' + + result = publish_agent.vip.rpc.call( + PLATFORM_ACTUATOR, # Target agent + 'set_point', # Method + agentid, # Requestor + 'fakedriver0/SampleWritableFloat1', # Point to set + 2.5 # New value + ).get(timeout=10) + assert result == 2.5 + + +@pytest.mark.actuator +def test_set_point_raises_type_error_on_setting_array(publish_agent, cancel_schedules): + """ + Test setting a array of single float value of a point. Should return + type error + + :param publish_agent: fixture invoked to setup all agents necessary and + returns an instance of Agent object used for publishing + :param cancel_schedules: fixture used to cancel the schedule at the end + of test so that other tests can use the same device and time slot + """ + # set agentid and task id for cancel_schedules fixture + agentid = TEST_AGENT + taskid = 'task_set_float_array_value' + cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) + + start = str(datetime.now()) + end = str(datetime.now() + timedelta(seconds=2)) + msg = [ + ['fakedriver0', start, end] + ] + result = publish_agent.vip.rpc.call( + PLATFORM_ACTUATOR, + REQUEST_NEW_SCHEDULE, + agentid, + taskid, + PRIORITY_LOW, + msg).get(timeout=10) + # expected result {'info': u'', 'data': {}, 'result': SUCCESS} + print(result) + assert result['result'] == SUCCESS + try: + result = publish_agent.vip.rpc.call( + PLATFORM_ACTUATOR, # Target agent + 'set_point', # Method + agentid, # Requestor + 'fakedriver0/SampleWritableFloat1', # Point to set + [2.5] # New value + ).get(timeout=10) + pytest.fail('Expecting RemoteError for trying to set array on point ' 'that expects float. Code returned {}'.format(result)) except RemoteError as e: assert "TypeError" in e.message @pytest.mark.actuator -def test_set_lock_error(publish_agent): +def test_set_point_raises_lock_error(publish_agent): """ Test setting a float value of a point through rpc without an allocation Expected result @@ -1517,11 +1372,9 @@ def test_set_lock_error(publish_agent): @pytest.mark.actuator -def test_set_value_error(publish_agent, cancel_schedules): +def test_set_point_raises_value_error(publish_agent, cancel_schedules): """ Test setting a wrong type value of a point through rpc - - :param publish_agent: fixture invoked to setup all agents necessary and returns an instance of Agent object used for publishing :param cancel_schedules: fixture used to cancel the schedule at the end @@ -1562,16 +1415,7 @@ def test_set_value_error(publish_agent, cancel_schedules): @pytest.mark.actuator -def test_set_error_read_only_point(publish_agent, cancel_schedules): - """ - Test setting a value of a read only point through pubsub - - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param cancel_schedules: fixture used to cancel the schedule at the end - of test so that other tests can use the same device and time slot - """ - print ("\n**** test_set_read_only_point ****") +def test_set_point_raises_remote_error_on_read_only_point(publish_agent, cancel_schedules): agentid = TEST_AGENT taskid = 'task_set_readonly_point' cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) @@ -1593,46 +1437,147 @@ def test_set_error_read_only_point(publish_agent, cancel_schedules): print(result) assert result['result'] == SUCCESS - try: - result = publish_agent.vip.rpc.call( + with pytest.raises(RemoteError): + publish_agent.vip.rpc.call( 'platform.actuator', # Target agent 'set_point', # Method agentid, # Requestor 'fakedriver0/OutsideAirTemperature1', # Point to set 1.2 # New value ).get(timeout=10) - pytest.fail( - 'Expecting RemoteError but code returned: {}'.format(result)) - except RemoteError as e: - assert "RuntimeError" in e.message + pytest.fail("Expecting remote error.") @pytest.mark.actuator -def test_get_multiple_points(publish_agent, cancel_schedules): - results, errors = publish_agent.vip.rpc.call( - 'platform.actuator', - 'get_multiple_points', - ['fakedriver0/SampleWritableFloat1', - 'fakedriver1/SampleWritableFloat1']).get(timeout=10) +def test_set_point_should_succeed_on_allow_no_lock_write_default_setting(publish_agent, volttron_instance): + """ Tests the default setting, 'allow_no_lock_write=True', to allow writing without a + lock as long as nothing else has the device locked. - assert results == {'fakedriver0/SampleWritableFloat1': 10.0, 'fakedriver1/SampleWritableFloat1': 1.0} - assert errors == {} + :param publish_agent: fixture invoked to setup all agents necessary and + returns an instance of Agent object used for publishing + :param volttron_instance: Volttron instance on which test is run + """ + + alternate_actuator_vip_id = "my_actuator" + # Use actuator that allows write with no lock (i.e. allow_no_lock_write=True) + my_actuator_uuid = volttron_instance.install_agent( + agent_dir=get_services_core("ActuatorAgent"), + config_file=get_services_core("ActuatorAgent/tests/actuator-no-lock.config"), + start=True, vip_identity=alternate_actuator_vip_id) + agentid = "" + try: + agentid = TEST_AGENT + + result = publish_agent.vip.rpc.call( + alternate_actuator_vip_id, # Target agent + 'set_point', # Method + agentid, # Requestor + 'fakedriver0/SampleWritableFloat1', # Point to set + 6.5 # New value + ).get(timeout=10) + assert result == approx(6.5) + + finally: + publish_agent.vip.rpc.call( + alternate_actuator_vip_id, # Target agent + 'revert_device', # Method + agentid, # Requestor + 'fakedriver0' # Point to revert + ).get(timeout=10) + + volttron_instance.stop_agent(my_actuator_uuid) + volttron_instance.remove_agent(my_actuator_uuid) @pytest.mark.actuator -def test_get_multiple_points_separate_pointname(publish_agent, cancel_schedules): - results, errors = publish_agent.vip.rpc.call( - 'platform.actuator', - 'get_multiple_points', - [['fakedriver0', 'SampleWritableFloat1'], - ['fakedriver1', 'SampleWritableFloat1']]).get(timeout=10) +def test_set_point_raises_remote_error_on_allow_no_lock_write_default_setting(publish_agent, volttron_instance): + """ Tests the default setting, 'allow_no_lock_write=True', to allow writing without a + lock as long as nothing else has the device locked. In this case, we schedule the devices, thereby + creating a lock. Upon setting a point when a lock is created, this test should raise a RemoteError. - assert results == {'fakedriver0/SampleWritableFloat1': 10.0, 'fakedriver1/SampleWritableFloat1': 1.0} - assert errors == {} + :param publish_agent: fixture invoked to setup all agents necessary and + returns an instance of Agent object used for publishing + :param volttron_instance: Volttron instance on which test is run + """ + + alternate_actuator_vip_id = "my_actuator" + # Use actuator that allows write with no lock. + my_actuator_uuid = volttron_instance.install_agent( + agent_dir=get_services_core("ActuatorAgent"), + config_file=get_services_core("ActuatorAgent/tests/actuator-no-lock.config"), + start=True, vip_identity=alternate_actuator_vip_id) + publish_agent2 = None + try: + agentid2 = "test-agent2" + taskid = "test-task" + publish_agent2 = volttron_instance.build_agent(identity=agentid2) + + start = str(datetime.now()) + end = str(datetime.now() + timedelta(seconds=60)) + msg = [ + ['fakedriver0', start, end] + ] + result = publish_agent2.vip.rpc.call( + alternate_actuator_vip_id, + REQUEST_NEW_SCHEDULE, + agentid2, + taskid, + PRIORITY_LOW, + msg).get(timeout=10) + # expected result {'info': u'', 'data': {}, 'result': SUCCESS} + print(result) + assert result['result'] == SUCCESS + + agentid = TEST_AGENT + with pytest.raises(RemoteError): + publish_agent.vip.rpc.call( + alternate_actuator_vip_id, # Target agent + 'set_point', # Method + agentid, # Requestor + 'fakedriver0/SampleWritableFloat1', # Point to set + 7.5 # New value + ).get(timeout=10) + pytest.fail("Expecting remote error.") + + finally: + publish_agent2.vip.rpc.call( + alternate_actuator_vip_id, # Target agent + 'revert_device', # Method + agentid, # Requestor + 'fakedriver0' # Point to revert + ).get(timeout=10) + + publish_agent2.core.stop() + volttron_instance.stop_agent(my_actuator_uuid) + volttron_instance.remove_agent(my_actuator_uuid) + + +@pytest.mark.actuator +def test_set_point_raises_remote_error_on_lock_failure(publish_agent, cancel_schedules): + """ + Test setting a float value of a point through rpc + Expected result = value of the actuation point + :param publish_agent: fixture invoked to setup all agents necessary and + returns an instance of Agent object used for publishing + :param cancel_schedules: fixture used to cancel the schedule at the end + of test so that other tests can use the same device and time slot + """ + print ("\n**** test_set_float_value ****") + agentid = TEST_AGENT + + with pytest.raises(RemoteError): + publish_agent.vip.rpc.call( + PLATFORM_ACTUATOR, # Target agent + 'set_point', # Method + agentid, # Requestor + 'fakedriver0/SampleWritableFloat1', # Point to set + 2.5 # New value + ).get(timeout=10) + pytest.fail("Expecting remote error.") @pytest.mark.actuator -def test_get_multiple_captures_errors(publish_agent, cancel_schedules): +def test_get_multiple_points_captures_errors_on_nonexistent_point(publish_agent, cancel_schedules): results, errors = publish_agent.vip.rpc.call( 'platform.actuator', 'get_multiple_points', @@ -1643,16 +1588,31 @@ def test_get_multiple_captures_errors(publish_agent, cancel_schedules): "DriverInterfaceError('Point not configured on device: nonexistentpoint',)" +@pytest.mark.parametrize("invalid_topics, topic_key", [ + ([42], '42'), + ([None], 'None'), +]) @pytest.mark.actuator -def test_get_multiple_captures_errors_invalid_point(publish_agent, cancel_schedules): - results, errors = publish_agent.vip.rpc.call('platform.actuator', 'get_multiple_points', [42]).get(timeout=10) - +def test_get_multiple_points_captures_errors_on_invalid_topic(publish_agent, cancel_schedules, invalid_topics, topic_key): + results, errors = publish_agent.vip.rpc.call('platform.actuator', 'get_multiple_points', invalid_topics).get(timeout=10) assert results == {} - assert errors['42'] == "ValueError('Invalid topic: 42',)" - - + assert errors[topic_key] == f"ValueError('Invalid topic: {topic_key}',)" + + +@pytest.mark.parametrize( + "topics_values_list", + [ + ( + [('fakedriver0/SampleWritableFloat1', 42), + ('fakedriver1/SampleWritableFloat1', 42)] + ), + ( + [(('fakedriver0', 'SampleWritableFloat1'), 42), + (('fakedriver1', 'SampleWritableFloat1'), 42)] + ) + ]) @pytest.mark.actuator -def test_set_multiple_points(publish_agent, cancel_schedules): +def test_set_multiple_points_should_succeed(publish_agent, cancel_schedules, topics_values_list): agentid = TEST_AGENT taskid0 = 'task_point_on_device_0' taskid1 = 'task_point_on_device_1' @@ -1690,19 +1650,28 @@ def test_set_multiple_points(publish_agent, cancel_schedules): 'platform.actuator', 'set_multiple_points', agentid, - [('fakedriver0/SampleWritableFloat1', 42), - ('fakedriver1/SampleWritableFloat1', 42)]).get(timeout=10) + topics_values_list).get(timeout=10) assert result == {} @pytest.mark.actuator -def test_set_multiple_points_separate_pointname(publish_agent, cancel_schedules): +def test_set_multiple_points_raises_remote_error_on_no_lock(publish_agent, cancel_schedules): agentid = TEST_AGENT - taskid0 = 'task_point_on_device_0' - taskid1 = 'task_point_on_device_1' - cancel_schedules.append({'agentid': agentid, 'taskid': taskid0}) - cancel_schedules.append({'agentid': agentid, 'taskid': taskid1}) + with pytest.raises(RemoteError): + publish_agent.vip.rpc.call( + 'platform.actuator', + 'set_multiple_points', + agentid, + [('fakedriver0/SampleWritableFloat1', 42)]).get(timeout=10) + pytest.fail("Expecting remote error.") + + +@pytest.mark.actuator +def test_set_multiple_points_captures_errors_on_read_only_point(publish_agent, cancel_schedules): + agentid = TEST_AGENT + taskid = 'task_point_on_device_0' + cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) start = str(datetime.now()) end = str(datetime.now() + timedelta(seconds=2)) @@ -1714,19 +1683,7 @@ def test_set_multiple_points_separate_pointname(publish_agent, cancel_schedules) PLATFORM_ACTUATOR, REQUEST_NEW_SCHEDULE, agentid, - taskid0, - PRIORITY_LOW, - msg).get(timeout=10) - assert result['result'] == SUCCESS - - msg = [ - ['fakedriver1', start, end] - ] - result = publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, - REQUEST_NEW_SCHEDULE, - agentid, - taskid1, + taskid, PRIORITY_LOW, msg).get(timeout=10) assert result['result'] == SUCCESS @@ -1735,33 +1692,23 @@ def test_set_multiple_points_separate_pointname(publish_agent, cancel_schedules) 'platform.actuator', 'set_multiple_points', agentid, - [(('fakedriver0', 'SampleWritableFloat1'), 42), - (('fakedriver1', 'SampleWritableFloat1'), 42)]).get(timeout=10) - - assert result == {} - - -@pytest.mark.actuator -def test_set_multiple_raises_lock_error(publish_agent, cancel_schedules): - agentid = TEST_AGENT + [('fakedriver0/OutsideAirTemperature1', 42)]).get(timeout=10) try: - result = publish_agent.vip.rpc.call( - 'platform.actuator', - 'set_multiple_points', - agentid, - [('fakedriver0/SampleWritableFloat1', 42)]).get(timeout=10) + r = result['fakedriver0/OutsideAirTemperature1'] + assert "RuntimeError" in r + except KeyError: + pytest.fail('read only point did not raise an exception') - pytest.fail('Expecting LockError. Code returned: {}'.format(result)) - except Exception as e: - # TODO - check exc_info - assert e.exc_info['exc_type'].endswith("LockError") - assert e.message == \ - "caller ({}) does not lock for device {}".format(TEST_AGENT, 'fakedriver0') + assert True +@pytest.mark.parametrize("invalid_topics, topic_key", [ + (42, '42'), + (None, 'None'), +]) @pytest.mark.actuator -def test_set_multiple_captures_errors(publish_agent, cancel_schedules): +def test_set_multiple_points_captures_errors_on_invalid_topic(publish_agent, cancel_schedules, invalid_topics, topic_key): agentid = TEST_AGENT taskid = 'task_point_on_device_0' cancel_schedules.append({'agentid': agentid, 'taskid': taskid}) @@ -1785,154 +1732,25 @@ def test_set_multiple_captures_errors(publish_agent, cancel_schedules): 'platform.actuator', 'set_multiple_points', agentid, - [('fakedriver0/OutsideAirTemperature1', 42)]).get(timeout=10) + [(invalid_topics, 42.42)]).get(timeout=10) - try: - r = result['fakedriver0/OutsideAirTemperature1'] - assert "RuntimeError" in r - except KeyError: - pytest.fail('read only point did not raise an exception') - - assert True - - -@pytest.mark.actuator -def test_scrape_all(publish_agent, cancel_schedules): - result = publish_agent.vip.rpc.call('platform.actuator', 'scrape_all', 'fakedriver0').get(timeout=10) - assert type(result) is dict - assert len(result) == 13 + assert result[topic_key] == f"ValueError('Invalid topic: {topic_key}',)" @pytest.mark.actuator -def test_set_value_no_lock(publish_agent, volttron_instance): - """ Tests the (now default) setting to allow writing without a - lock as long as nothing else has the device locked. - - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param volttron_instance: Volttron instance on which test is run - """ - - alternate_actuator_vip_id = "my_actuator" - # Use actuator that allows write with no lock. - my_actuator_uuid = volttron_instance.install_agent( - agent_dir=get_services_core("ActuatorAgent"), - config_file=get_services_core("ActuatorAgent/tests/actuator-no-lock.config"), - start=True, vip_identity=alternate_actuator_vip_id) - agentid = "" - try: - agentid = TEST_AGENT +def test_scrape_all_should_succeed(publish_agent, cancel_schedules, volttron_instance): + points_filename = f"{volttron_instance.volttron_root}/scripts/scalability-testing/fake_unit_testing.csv" + with open(points_filename) as f: + expected_count_points = sum(1 for _ in f) - 1 - result = publish_agent.vip.rpc.call( - alternate_actuator_vip_id, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver0/SampleWritableFloat1', # Point to set - 6.5 # New value - ).get(timeout=10) - assert result == approx(6.5) - - finally: - publish_agent.vip.rpc.call( - alternate_actuator_vip_id, # Target agent - 'revert_device', # Method - agentid, # Requestor - 'fakedriver0' # Point to revert - ).get(timeout=10) - - volttron_instance.stop_agent(my_actuator_uuid) - volttron_instance.remove_agent(my_actuator_uuid) - - -@pytest.mark.actuator -def test_set_value_no_lock_failure(publish_agent, volttron_instance): - """ Tests the (now default) setting to allow writing without a - lock as long as nothing else has the device locked. - - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param volttron_instance: Volttron instance on which test is run - """ - - alternate_actuator_vip_id = "my_actuator" - # Use actuator that allows write with no lock. - my_actuator_uuid = volttron_instance.install_agent( - agent_dir=get_services_core("ActuatorAgent"), - config_file=get_services_core("ActuatorAgent/tests/actuator-no-lock.config"), - start=True, vip_identity=alternate_actuator_vip_id) - publish_agent2 = None - try: - agentid2 = "test-agent2" - taskid = "test-task" - publish_agent2 = volttron_instance.build_agent(identity=agentid2) - - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=60)) - msg = [ - ['fakedriver0', start, end] - ] - result = publish_agent2.vip.rpc.call( - alternate_actuator_vip_id, - REQUEST_NEW_SCHEDULE, - agentid2, - taskid, - PRIORITY_LOW, - msg).get(timeout=10) - # expected result {'info': u'', 'data': {}, 'result': SUCCESS} - print(result) - assert result['result'] == SUCCESS - - agentid = TEST_AGENT - - with pytest.raises(RemoteError): - publish_agent.vip.rpc.call( - alternate_actuator_vip_id, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver0/SampleWritableFloat1', # Point to set - 7.5 # New value - ).get(timeout=10) - pytest.fail("Expecting remote error.") - - finally: - publish_agent2.vip.rpc.call( - alternate_actuator_vip_id, # Target agent - 'revert_device', # Method - agentid, # Requestor - 'fakedriver0' # Point to revert - ).get(timeout=10) - - publish_agent2.core.stop() - volttron_instance.stop_agent(my_actuator_uuid) - volttron_instance.remove_agent(my_actuator_uuid) - - -@pytest.mark.actuator -def test_set_value_float_failure(publish_agent, cancel_schedules): - """ - Test setting a float value of a point through rpc - Expected result = value of the actuation point - :param publish_agent: fixture invoked to setup all agents necessary and - returns an instance of Agent object used for publishing - :param cancel_schedules: fixture used to cancel the schedule at the end - of test so that other tests can use the same device and time slot - """ - print ("\n**** test_set_float_value ****") - agentid = TEST_AGENT + result = publish_agent.vip.rpc.call('platform.actuator', 'scrape_all', 'fakedriver0').get(timeout=10) - with pytest.raises(RemoteError): - publish_agent.vip.rpc.call( - PLATFORM_ACTUATOR, # Target agent - 'set_point', # Method - agentid, # Requestor - 'fakedriver0/SampleWritableFloat1', # Point to set - 2.5 # New value - ).get(timeout=10) - pytest.fail("Expecting remote error.") + assert type(result) is dict + assert len(result) == expected_count_points @pytest.mark.actuator -def test_actuator_default_config(volttron_instance, publish_agent): +def test_actuator_default_config_should_succeed(volttron_instance, publish_agent): """ Test the default configuration file included with the agent """ diff --git a/services/core/ActuatorAgent/tests/test_actuator_rpc_unit.py b/services/core/ActuatorAgent/tests/test_actuator_rpc_unit.py deleted file mode 100644 index 38489bf6f3..0000000000 --- a/services/core/ActuatorAgent/tests/test_actuator_rpc_unit.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- coding: utf-8 -*- {{{ -# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: -# -# Copyright 2020, Battelle Memorial Institute. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# This material was prepared as an account of work sponsored by an agency of -# the United States Government. Neither the United States Government nor the -# United States Department of Energy, nor Battelle, nor any of their -# employees, nor any jurisdiction or organization that has cooperated in the -# development of these materials, makes any warranty, express or -# implied, or assumes any legal liability or responsibility for the accuracy, -# completeness, or usefulness or any information, apparatus, product, -# software, or process disclosed, or represents that its use would not infringe -# privately owned rights. Reference herein to any specific commercial product, -# process, or service by trade name, trademark, manufacturer, or otherwise -# does not necessarily constitute or imply its endorsement, recommendation, or -# favoring by the United States Government or any agency thereof, or -# Battelle Memorial Institute. The views and opinions of authors expressed -# herein do not necessarily state or reflect those of the -# United States Government or any agency thereof. -# -# PACIFIC NORTHWEST NATIONAL LABORATORY operated by -# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY -# under Contract DE-AC05-76RL01830 -# }}} - -""" -Unit test cases for testing actuator agent using rpc calls. -""" -import logging -from datetime import datetime, timedelta - -import pytest - -from services.core.ActuatorAgent.actuator import agent -from services.core.ActuatorAgent.actuator.agent import ActuatorAgent, LockError -from services.core.ActuatorAgent.actuator.scheduler import RequestResult, DeviceState -from services.core.ActuatorAgent.tests.actuator_fixtures import MockedAsyncResult, \ - get_actuator_agent -from volttrontesting.utils.utils import AgentMock -from volttron.platform.vip.agent import Agent - -pytestmark = [pytest.mark.actuator_unit, pytest.mark.unit] - -PRIORITY_LOW = "LOW" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -REQUESTER_ID = "foo" -TASK_ID = "task-id" -TIME_SLOT_REQUESTS = [ - ["fakedriver0", str(datetime.now()), str(datetime.now() + timedelta(seconds=1))] -] - -agent._log = logging.getLogger("test_logger") -ActuatorAgent.__bases__ = (AgentMock.imitate(Agent, Agent()),) - - -@pytest.mark.parametrize("topic, point", [("path/topic", None), ("another/path/to/topic", 42)]) -def test_get_point_should_succeed(topic, point): - with get_actuator_agent(vip_rpc_call_res=MockedAsyncResult(10.0)) as actuator_agent: - result = actuator_agent.get_point(topic, point=point) - - actuator_agent.vip.rpc.call.assert_called_once() - assert result is not None - - -@pytest.mark.parametrize( - "point, device_state", - [ - ( - 42, - {"foo/bar": DeviceState("requester-id-1", "task-id-1", "anytime")}, - ), - ( - None, - {"foo": DeviceState("requester-id-1", "task-id-1", "anytime")}), - ], -) -def test_set_point_should_succeed(point, device_state): - requester_id = "requester-id-1" - topic = "foo/bar" - value = "some value" - - with get_actuator_agent(vip_message_peer=requester_id, device_state=device_state) as \ - actuator_agent: - result = actuator_agent.set_point(requester_id, topic, value, point=point) - - assert result is not None - - -@pytest.mark.parametrize("rpc_peer", [None, 42, []]) -def test_set_point_should_raise_type_error(rpc_peer): - with pytest.raises(TypeError, match="Agent id must be a nonempty string"): - requester_id = "requester-id-1" - topic = "foo/bar" - value = "some value" - point = None - - with get_actuator_agent(vip_message_peer=rpc_peer) as actuator_agent: - actuator_agent.set_point(requester_id, topic, value, point=point) - - -def test_set_point_should_raise_lock_error_on_non_matching_device(): - with pytest.raises(LockError): - requester_id = "requester-id-1" - topic = "foo/bar" - value = "some value" - - with get_actuator_agent(vip_message_peer="some rpc_peer") as actuator_agent: - actuator_agent.set_point(requester_id, topic, value) - - -def test_scrape_all_should_succeed(): - with get_actuator_agent(vip_rpc_call_res=MockedAsyncResult({})) as actuator_agent: - topic = "whan/that/aprille" - - result = actuator_agent.scrape_all(topic) - - assert isinstance(result, dict) - - - -@pytest.mark.parametrize( - "topics", - [ - ["foo/bar"], - ["foo/bar", "sna/foo"], - [["dev1", "point1"]], - [["dev1", "point1"], ["dev2", "point2"]], - ], -) -def test_get_multiple_points_should_succeed(topics): - mocked_rpc_call_res = MockedAsyncResult(({"result": "value"}, {})) - with get_actuator_agent(vip_rpc_call_res=mocked_rpc_call_res) as actuator_agent: - results, errors = actuator_agent.get_multiple_points(topics) - - assert isinstance(results, dict) - assert isinstance(errors, dict) - assert len(errors) == 0 - - -@pytest.mark.parametrize("invalid_topics", [[(123,)], [(None)], [[123]], [[None]]]) -def test_get_multiple_points_should_return_errors(invalid_topics): - with get_actuator_agent() as actuator_agent: - - results, errors = actuator_agent.get_multiple_points(invalid_topics) - - assert isinstance(results, dict) - assert isinstance(errors, dict) - assert len(errors) == 1 - - -@pytest.mark.parametrize( - "topic_values, device_state", - [ - ([], {}), - ( - [("foo/bar", "roma_value")], - {"foo": DeviceState("requester-id-1", "task-id-1", "anytime")}, - ), - ( - [("foo/bar", "roma_value"), ("sna/fu", "amor_value")], - { - "foo": DeviceState("requester-id-1", "task-id-1", "anytime"), - "sna": DeviceState("requester-id-1", "task-id-1", "anytime"), - }, - ), - ], -) -def test_set_multiple_points_should_succeed(topic_values, device_state): - requester_id = "requester-id-1" - mocked_rpc_call_res = MockedAsyncResult(({})) - with get_actuator_agent(vip_message_peer=requester_id, device_state=device_state, - vip_rpc_call_res=mocked_rpc_call_res) as actuator_agent: - result = actuator_agent.set_multiple_points("request-id-1", topic_values) - - assert result == {} - - -@pytest.mark.parametrize("invalid_topic_values", [[(None,)], [(1234,)]]) -def test_set_multiple_points_should_raise_value_error(invalid_topic_values): - with pytest.raises(ValueError): - requester_id = "requester-id-1" - - with get_actuator_agent(vip_message_peer=requester_id) as actuator_agent: - actuator_agent.set_multiple_points("request-id-1", invalid_topic_values) - - -def test_set_multiple_points_should_raise_lock_error_on_empty_devices(): - with pytest.raises(LockError): - requester_id = "requester-id-1" - topic_values = [("foo/bar", "roma_value")] - - with get_actuator_agent(vip_message_peer=requester_id) as actuator_agent: - actuator_agent.set_multiple_points("request-id-1", topic_values) - - -def test_set_multiple_points_should_raise_lock_error_on_non_matching_requester(): - with pytest.raises(LockError): - requester_id = "wrong-requester" - topic_values = [("foo/bar", "roma_value")] - device_state = { - "foo": DeviceState("requester-id-1", "task-id-1", "anytime") - } - - with get_actuator_agent(vip_message_peer=requester_id, device_state=device_state) \ - as actuator_agent: - actuator_agent.set_multiple_points("request-id-1", topic_values) - - -@pytest.mark.parametrize("point", [None, "foobarpoint"]) -def test_revert_point_should_raise_lock_error_on_empty_devices(point): - with pytest.raises(LockError): - requester_id = "request-id-1" - topic = "foo/bar" - - with get_actuator_agent(vip_message_peer="requester-id-1") as actuator_agent: - actuator_agent.revert_point(requester_id, topic, point=point) - - -@pytest.mark.parametrize("point", [None, "foobarpoint"]) -def test_revert_point_should_raise_lock_error_on_non_matching_requester(point): - with pytest.raises(LockError): - device_state = { - "foo": DeviceState("requester-id-1", "task-id-1", "anytime") - } - requester_id = "request-id-1" - topic = "foo/bar" - - with get_actuator_agent(vip_message_peer="wrong-requester", device_state=device_state) \ - as actuator_agent: - actuator_agent.revert_point(requester_id, topic, point=point) - - -def test_revert_device_should_raise_lock_error_on_empty_devices(): - with pytest.raises(LockError): - requester_id = "request-id-1" - topic = "foo/bar" - - with get_actuator_agent(vip_message_peer="requester-id-1") as actuator_agent: - actuator_agent.revert_device(requester_id, topic) - - -def test_revert_device_should_raise_lock_error_on_non_matching_requester(): - with pytest.raises(LockError): - device_state = { - "foo/bar": DeviceState("requester-id-1", "task-id-1", "anytime") - } - requester_id = "request-id-1" - topic = "foo/bar" - - with get_actuator_agent(vip_message_peer="wrong-requester", device_state=device_state) \ - as actuator_agent: - actuator_agent.revert_device(requester_id, topic) - - -def test_request_new_schedule_should_succeed(): - with get_actuator_agent() as actuator_agent: - result = actuator_agent.request_new_schedule(REQUESTER_ID, TASK_ID, - PRIORITY_LOW, TIME_SLOT_REQUESTS) - - assert result["result"] == SUCCESS - - -def test_request_new_schedule_should_succeed_when_stop_start_times_overlap(): - start = str(datetime.now()) - end = str(datetime.now() + timedelta(seconds=1)) - end2 = str(datetime.now() + timedelta(seconds=2)) - time_slot_requests = [["fakedriver0", start, end], ["fakedriver0", end, end2]] - - with get_actuator_agent() as actuator_agent: - result = actuator_agent.request_new_schedule(REQUESTER_ID, TASK_ID, - PRIORITY_LOW, time_slot_requests) - - assert result["result"] == SUCCESS - - -@pytest.mark.parametrize( - "task_id, expected_info", - [ - (1234, "MALFORMED_REQUEST: TypeError: taskid must be a nonempty string"), - ("", "MALFORMED_REQUEST: TypeError: taskid must be a nonempty string"), - (None, "MISSING_TASK_ID"), - ("task-id-duplicate", "TASK_ID_ALREADY_EXISTS"), - ], -) -def test_request_new_schedule_should_fail_on_invalid_taskid(task_id, expected_info): - false_request_result = RequestResult(False, {}, expected_info) - - with get_actuator_agent(slot_requests_res=false_request_result) as actuator_agent: - result = actuator_agent.request_new_schedule(REQUESTER_ID, task_id, - PRIORITY_LOW, TIME_SLOT_REQUESTS) - - assert result["result"] == FAILURE - assert result["info"] == expected_info - - -@pytest.mark.parametrize( - "invalid_priority, expected_info", - [("LOW2", "INVALID_PRIORITY"), (None, "MISSING_PRIORITY")], -) -def test_request_new_schedule_should_fail_on_invalid_priority(invalid_priority, expected_info): - false_request_result = RequestResult(False, {}, expected_info) - - with get_actuator_agent(slot_requests_res=false_request_result) as actuator_agent: - result = actuator_agent.request_new_schedule(REQUESTER_ID, TASK_ID, - invalid_priority, TIME_SLOT_REQUESTS) - - assert result["result"] == FAILURE - assert result["info"] == expected_info - - -@pytest.mark.parametrize( - "time_slot_request, expected_info", - [ - ( - [], - "MALFORMED_REQUEST_EMPTY"), - ( - [["fakedriver0", str(datetime.now()), ""]], - "MALFORMED_REQUEST: ParserError: String does not contain a date: ", - ), - ( - [["fakedriver0", str(datetime.now())]], - "MALFORMED_REQUEST: ValueError: " - "not enough values to unpack (expected 3, got 2)", - ), - ], -) -def test_request_new_schedule_should_fail_invalid_time_slot_requests(time_slot_request, - expected_info): - false_request_result = RequestResult(False, {}, expected_info) - - with get_actuator_agent(slot_requests_res=false_request_result) as actuator_agent: - result = actuator_agent.request_new_schedule( - REQUESTER_ID, TASK_ID, PRIORITY_LOW, time_slot_request - ) - - assert result["result"] == FAILURE - assert result["info"] == expected_info - - -def test_request_cancel_schedule_should_succeed_happy_path(): - true_request_result = RequestResult( - True, {}, "" - ) - - with get_actuator_agent(cancel_schedule_result=true_request_result) as actuator_agent: - result = actuator_agent.request_cancel_schedule(REQUESTER_ID, TASK_ID) - - assert result["result"] == SUCCESS - - -def test_request_cancel_schedule_should_fail_on_invalid_task_id(): - false_request_result = RequestResult( - False, {}, "TASK_ID_DOES_NOT_EXIST" - ) - invalid_task_id = "invalid-task-id" - - with get_actuator_agent(cancel_schedule_result=false_request_result) as actuator_agent: - result = actuator_agent.request_cancel_schedule(REQUESTER_ID, invalid_task_id) - - assert result["result"] == FAILURE - assert result["info"] == "TASK_ID_DOES_NOT_EXIST" From 15190b57fb129768547ca62486a9fe293c46b826 Mon Sep 17 00:00:00 2001 From: sgilbride Date: Mon, 17 May 2021 21:35:52 -0700 Subject: [PATCH 11/46] Modified get_user_claims_from_bearer to ignore the built-in verify_exp capabilities of jwt.decode to handle the expiration ourselves. Modified logic to handle expired claim to match desired use case and to raise NotAuthorized in the case of failure to take advantage of the error handling in renew_auth_token. Added tests to verify functionality. --- volttron/platform/web/__init__.py | 12 +- .../platform/web/test_web_authentication.py | 209 +++++++++++------- 2 files changed, 142 insertions(+), 79 deletions(-) diff --git a/volttron/platform/web/__init__.py b/volttron/platform/web/__init__.py index 0f0d4eece9..0b3f9f97fb 100644 --- a/volttron/platform/web/__init__.py +++ b/volttron/platform/web/__init__.py @@ -124,7 +124,15 @@ def get_user_claim_from_bearer(bearer, web_secret_key=None, tls_public_key=None) pubkey = tls_public_key # if isinstance(tls_public_key, str): # pubkey = CertWrapper.load_cert(tls_public_key) - claims = jwt.decode(bearer, pubkey, algorithms=algorithm) + + claims = jwt.decode(bearer, pubkey, algorithms=algorithm, options={"verify_exp": False}) + exp = datetime.utcfromtimestamp(claims['exp']) nbf = datetime.utcfromtimestamp(claims['nbf']) - return claims if not (exp < datetime.utcnow() <= nbf) else {} + now = datetime.utcnow() + # Now must be before expiration time and after Not Before time + + if nbf <= now < exp: + return claims + else: + raise NotAuthorized diff --git a/volttrontesting/platform/web/test_web_authentication.py b/volttrontesting/platform/web/test_web_authentication.py index d47facc5f2..e68d995e0b 100644 --- a/volttrontesting/platform/web/test_web_authentication.py +++ b/volttrontesting/platform/web/test_web_authentication.py @@ -1,6 +1,8 @@ - +import json from urllib.parse import urlencode +import gevent +from datetime import datetime, timedelta from mock import MagicMock from deepdiff import DeepDiff import jwt @@ -40,16 +42,116 @@ def test_jwt_encode(encryption_type): assert not DeepDiff(claims, new_claimes) +# Child of AuthenticateEndpoints. +# Exactly the same but includes helper methods to set access and refresh token timeouts +class MockAuthenticateEndpoints(AuthenticateEndpoints): + def set_refresh_token_timeout(self, timeout): + self.refresh_token_timeout = timeout + def set_access_token_timeout(self, timeout): + self.access_token_timeout = timeout + +# Setup test values for authenticate tests +def set_test_admin(): + authorize_ep = MockAuthenticateEndpoints(web_secret_key=get_random_key()) + authorize_ep.set_access_token_timeout(0.1) + authorize_ep.set_refresh_token_timeout(0.2) + AdminEndpoints().add_user("test_admin", "Pass123", groups=['admin']) + test_user = {"username": "test_admin", "password": "Pass123"} + gevent.sleep(1) + return authorize_ep, test_user + +def test_authenticate_get_request_fails(): + with get_test_volttron_home(messagebus='zmq'): + authorize_ep, test_user = set_test_admin() + env = get_test_web_env('/authenticate', method='GET') + response = authorize_ep.handle_authenticate(env, test_user) + assert ('Content-Type', 'text/plain') in response.headers.items() + assert '405 Method Not Allowed' in response.status -def test_authenticate_must_use_post_request(): +def test_authenticate_post_request(): with get_test_volttron_home(messagebus='zmq'): + authorize_ep, test_user = set_test_admin() + env = get_test_web_env('/authenticate', method='POST') + response = authorize_ep.handle_authenticate(env, test_user) + assert ('Content-Type', 'application/json') in response.headers.items() + assert '200 OK' in response.status + response_token = json.loads(response.response[0].decode('utf-8')) + refresh_token = response_token['refresh_token'] + access_token = response_token["access_token"] + assert 3 == len(refresh_token.split('.')) + assert 3 == len(access_token.split(".")) + + +def test_authenticate_put_request(): + with get_test_volttron_home(messagebus='zmq'): + + authorize_ep, test_user = set_test_admin() + # Get tokens for test + env = get_test_web_env('/authenticate', method='POST') + response = authorize_ep.handle_authenticate(env, test_user) + response_token = json.loads(response.response[0].decode('utf-8')) + refresh_token = response_token['refresh_token'] + access_token = response_token["access_token"] + + # Test PUT Request + env = get_test_web_env('/authenticate', method='PUT') + env["HTTP_AUTHORIZATION"] = "BEARER " + refresh_token + response = authorize_ep.handle_authenticate(env, data={}) + assert ('Content-Type', 'application/json') in response.headers.items() + assert '200 OK' in response.status - env = get_test_web_env('/authenticate', method='GET') - authorize_ep = AuthenticateEndpoints(web_secret_key=get_random_key()) - response = authorize_ep.get_auth_token(env, {}) +def test_authenticate_put_request_access_expires(): + with get_test_volttron_home(messagebus='zmq'): + + authorize_ep, test_user = set_test_admin() + # Get tokens for test + env = get_test_web_env('/authenticate', method='POST') + response = authorize_ep.handle_authenticate(env, test_user) + response_token = json.loads(response.response[0].decode('utf-8')) + refresh_token = response_token['refresh_token'] + access_token = response_token["access_token"] + + # Get access token after previous token expires. Verify they are different + gevent.sleep(7) + env = get_test_web_env('/authenticate', method='PUT') + env["HTTP_AUTHORIZATION"] = "BEARER " + refresh_token + response = authorize_ep.handle_authenticate(env, data={}) + assert ('Content-Type', 'application/json') in response.headers.items() + assert '200 OK' in response.status + assert access_token != json.loads(response.response[0].decode('utf-8'))["access_token"] + +def test_authenticate_put_request_refresh_expires(): + with get_test_volttron_home(messagebus='zmq'): + + authorize_ep, test_user = set_test_admin() + # Get tokens for test + env = get_test_web_env('/authenticate', method='POST') + response = authorize_ep.handle_authenticate(env, test_user) + response_token = json.loads(response.response[0].decode('utf-8')) + refresh_token = response_token['refresh_token'] + access_token = response_token["access_token"] + + # Wait for refresh token to expire + gevent.sleep(20) + env = get_test_web_env('/authenticate', method='PUT') + env["HTTP_AUTHORIZATION"] = "BEARER " + refresh_token + response = authorize_ep.handle_authenticate(env, data={}) assert ('Content-Type', 'text/html') in response.headers.items() - assert '401 Unauthorized' in response.status + assert "401 Unauthorized" in response.status + +def test_authenticate_delete_request(): + with get_test_volttron_home(messagebus='zmq'): + authorize_ep, test_user = set_test_admin() + # Get tokens for test + env = get_test_web_env('/authenticate', method='POST') + response = authorize_ep.handle_authenticate(env, test_user) + + # Touch Delete endpoint + env = get_test_web_env('/authenticate', method='DELETE') + response = authorize_ep.handle_authenticate(env, test_user) + assert ('Content-Type', 'text/plain') in response.headers.items() + assert '501 Not Implemented' in response.status def test_no_private_key_or_passphrase(): @@ -67,54 +169,6 @@ def test_both_private_key_and_passphrase(): tls_private_key=certs.server_certs[0].key) -@pytest.mark.parametrize("scheme", ("http", "https")) -def test_authenticate_endpoint(scheme): - kwargs = {} - - # Note this is not a context wrapper, it just does the creation for us - vhome = create_volttron_home() - - if scheme == 'https': - with certs_profile_1(vhome) as certs: - kwargs['web_ssl_key'] = certs.server_certs[0].key_file - kwargs['web_ssl_cert'] = certs.server_certs[0].cert_file - else: - kwargs['web_secret_key'] = get_random_key() - - # We are specifying the volttron_home here so we don't create an additional one. - with get_test_volttron_home(messagebus='zmq', config_params=kwargs, volttron_home=vhome): - - user = 'bogart' - passwd = 'cat' - adminep = AdminEndpoints() - adminep.add_user(user, passwd) - - env = get_test_web_env('/authenticate', method='POST') - - if scheme == 'http': - authorizeep = AuthenticateEndpoints(web_secret_key=kwargs.get('web_secret_key')) - else: - authorizeep = AuthenticateEndpoints(tls_private_key=CertWrapper.load_key(kwargs.get('web_ssl_key'))) - - invalid_login_username_params = dict(username='fooey', password=passwd) - - response = authorizeep.get_auth_token(env, invalid_login_username_params) - - assert '401' == response.status - # TODO: Get the actual response content here - # assert '401 Unauthorized' in response.content - - invalid_login_password_params = dict(username=user, password='hazzah') - response = authorizeep.get_auth_token(env, invalid_login_password_params) - - assert '401' == response.status - valid_login_params = urlencode(dict(username=user, password=passwd)) - response = authorizeep.get_auth_token(env, valid_login_params) - assert '200 OK' == response.status - assert "text/plain" in response.content_type - assert 3 == len(response.response[0].decode('utf-8').split('.')) - - @pytest.fixture() def mock_platformweb_service(): PlatformWebService.__bases__ = (AgentMock.imitate(Agent, Agent()),) @@ -123,26 +177,27 @@ def mock_platformweb_service(): platformweb._admin_endpoints = AdminEndpoints(rpc_caller=rpc_caller) yield platformweb - -@pytest.mark.web -def test_get_credentials(mock_platformweb_service): - mock_platformweb_service._admin_endpoints._pending_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_pending') - mock_platformweb_service._admin_endpoints._denied_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_denied') - pass - - -@pytest.mark.web -def test_accept_credential(mock_platformweb_service): - mock_platformweb_service._admin_endpoints._pending_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_pending').get() - mock_platformweb_service._admin_endpoints._denied_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_denied').get() - pass - - -@pytest.mark.web -def test_deny_credential(): - pass - - -@pytest.mark.web -def test_delete_credential(): - pass +# TODO: These tests are updated in PR#2650 + +# @pytest.mark.web +# def test_get_credentials(mock_platformweb_service): +# mock_platformweb_service._admin_endpoints._pending_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_pending') +# mock_platformweb_service._admin_endpoints._denied_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_denied') +# pass +# +# +# @pytest.mark.web +# def test_accept_credential(mock_platformweb_service): +# mock_platformweb_service._admin_endpoints._pending_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_pending').get() +# mock_platformweb_service._admin_endpoints._denied_auths = mock_platformweb_service._admin_endpoints._rpc_caller.call(AUTH, 'get_authorization_denied').get() +# pass +# +# +# @pytest.mark.web +# def test_deny_credential(): +# pass +# +# +# @pytest.mark.web +# def test_delete_credential(): +# pass From f93f441654486a2f6ad6386cea53934f4c699497 Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Wed, 19 May 2021 07:49:38 -0700 Subject: [PATCH 12/46] Update postgresqlfuncts.py --- volttron/platform/dbutils/postgresqlfuncts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volttron/platform/dbutils/postgresqlfuncts.py b/volttron/platform/dbutils/postgresqlfuncts.py index 39a69c5da6..af964a88b7 100644 --- a/volttron/platform/dbutils/postgresqlfuncts.py +++ b/volttron/platform/dbutils/postgresqlfuncts.py @@ -121,7 +121,8 @@ def setup_historian_tables(self): "SELECT create_hypertable({}, 'ts', if_not_exists => true)").format( Literal(self.data_table))) self.execute_stmt(SQL( - 'CREATE INDEX ON {} (topic_id, ts)').format( + 'CREATE INDEX IF NOT EXISTS {} ON {} (topic_id, ts)').format( + Identifier('idx_' + self.data_table), Identifier(self.data_table))) else: self.execute_stmt(SQL( From 01d0774a49e33d569f73307fdab2b198533f018c Mon Sep 17 00:00:00 2001 From: sgilbride Date: Fri, 28 May 2021 12:07:45 -0700 Subject: [PATCH 13/46] Modified get_user_claims_from_bearer to use jwt.decode's built-in expired signature verification. Modified renew_auth_token to catch jwt.ExpiredSignatureError if the refresh token has expired. --- volttron/platform/web/__init__.py | 13 ++----------- volttron/platform/web/authenticate_endpoint.py | 4 ++++ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/volttron/platform/web/__init__.py b/volttron/platform/web/__init__.py index 0b3f9f97fb..c9bffba28a 100644 --- a/volttron/platform/web/__init__.py +++ b/volttron/platform/web/__init__.py @@ -125,14 +125,5 @@ def get_user_claim_from_bearer(bearer, web_secret_key=None, tls_public_key=None) # if isinstance(tls_public_key, str): # pubkey = CertWrapper.load_cert(tls_public_key) - claims = jwt.decode(bearer, pubkey, algorithms=algorithm, options={"verify_exp": False}) - - exp = datetime.utcfromtimestamp(claims['exp']) - nbf = datetime.utcfromtimestamp(claims['nbf']) - now = datetime.utcnow() - # Now must be before expiration time and after Not Before time - - if nbf <= now < exp: - return claims - else: - raise NotAuthorized + claims = jwt.decode(bearer, pubkey, algorithms=algorithm) + return claims diff --git a/volttron/platform/web/authenticate_endpoint.py b/volttron/platform/web/authenticate_endpoint.py index 25b3afa56c..ebfe561194 100644 --- a/volttron/platform/web/authenticate_endpoint.py +++ b/volttron/platform/web/authenticate_endpoint.py @@ -190,6 +190,10 @@ def renew_auth_token(self, env, data): _log.error("Unauthorized user attempted to connect to {}".format(env.get('PATH_INFO'))) return Response('Unauthorized User', status="401 Unauthorized") + except jwt.ExpiredSignatureError: + _log.error("User attempted to connect to {} with an expired signature".format(env.get('PATH_INFO'))) + return Response('Unauthorized User', status="401 Unauthorized") + if claims.get('grant_type') != 'refresh_token' or not claims.get('groups'): return Response('Invalid refresh token.', status="401 Unauthorized") else: From ae92a69c400090a5d7e9467d17d0ac36ef80aafb Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Thu, 3 Jun 2021 12:20:44 -0700 Subject: [PATCH 14/46] Add sleep for config_store_connection fixture --- .../PlatformDriverAgent/tests/test_device_groups.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/core/PlatformDriverAgent/tests/test_device_groups.py b/services/core/PlatformDriverAgent/tests/test_device_groups.py index b7e14b313d..2c306a48be 100644 --- a/services/core/PlatformDriverAgent/tests/test_device_groups.py +++ b/services/core/PlatformDriverAgent/tests/test_device_groups.py @@ -75,7 +75,7 @@ def add_result(self, peer, sender, bus, topic, headers, message): @pytest.fixture(scope="module") -def subscriber_agent(request, volttron_instance): +def subscriber_agent(volttron_instance): agent = volttron_instance.build_agent(identity='subscriber_agent', agent_class=_subscriber_agent) @@ -118,9 +118,10 @@ def subscriber_agent(request, volttron_instance): @pytest.fixture(scope="module") -def config_store_connection(request, volttron_instance): +def config_store_connection(volttron_instance): capabilities = [{'edit_config_store': {'identity': PLATFORM_DRIVER}}] connection = volttron_instance.build_connection(peer=CONFIGURATION_STORE, capabilities=capabilities) + gevent.sleep(1) # Reset platform driver config store connection.call("manage_delete_store", PLATFORM_DRIVER) @@ -141,14 +142,14 @@ def config_store_connection(request, volttron_instance): @pytest.fixture(scope="function") -def config_store(request, config_store_connection): +def config_store(config_store_connection): # Always have fake.csv ready to go. print("Adding fake.csv into store") config_store_connection.call("manage_store", PLATFORM_DRIVER, "fake.csv", registry_config_string, config_type="csv") yield config_store_connection - # Reset platform driver config store - print("Wiping out store.") + + print("Resetting platform driver config store") config_store_connection.call("manage_delete_store", PLATFORM_DRIVER) gevent.sleep(0.1) From 142f682257a225bfe3843c926d351c50c83cc769 Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Fri, 4 Jun 2021 18:22:41 -0700 Subject: [PATCH 15/46] Delete defaultLogConfig.ini @acedrew getting your delete in here #2680 --- examples/defaultLogConfig.ini | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 examples/defaultLogConfig.ini diff --git a/examples/defaultLogConfig.ini b/examples/defaultLogConfig.ini deleted file mode 100644 index 8d6f9aec8f..0000000000 --- a/examples/defaultLogConfig.ini +++ /dev/null @@ -1,30 +0,0 @@ -[loggers] -keys=root,debug01 - -[handlers] -keys=h_rotating - -[formatters] -keys=f_rotating - -[logger_root] -level=NOTSET -handlers=h_rotating - -[logger_debug01] -level=DEBUG -handlers=h_rotationg -propagate=1 -qualname=debug.log - -[handler_h_rotating] -class=TimedRotatingFileHandler -level=DEBUG -formatter=f_rotating -args=(volttron.log,when='midnight',backupCount=7) - -# Looks like the formatter may get overwritten by the code -[formatter_f_rotating] -format= -datefmt= -class=logging.Formatter From 245cd15c71e8e2866c271d7a585be97a7351abf6 Mon Sep 17 00:00:00 2001 From: Mark Bonicillo Date: Mon, 7 Jun 2021 11:05:45 -0700 Subject: [PATCH 16/46] Add pytest markers for contributed agent tests; add sleep to global_override and settings tests --- pytest.ini | 1 + .../interfaces/chargepoint/tests/test_chargepoint_driver.py | 2 ++ services/core/PlatformDriverAgent/tests/test_eagle.py | 2 ++ services/core/PlatformDriverAgent/tests/test_global_override.py | 1 + services/core/PlatformDriverAgent/tests/test_global_settings.py | 1 + 5 files changed, 7 insertions(+) diff --git a/pytest.ini b/pytest.ini index 6313fcb010..9a0a5b6799 100644 --- a/pytest.ini +++ b/pytest.ini @@ -62,3 +62,4 @@ markers = sqlitefuncts: level one integration tests for sqlitefuncts unit: Run all unit/level one integration tests influxdbutils: level one integration tests for influxdb + contrib: tests for community-contributed agents diff --git a/services/core/PlatformDriverAgent/platform_driver/interfaces/chargepoint/tests/test_chargepoint_driver.py b/services/core/PlatformDriverAgent/platform_driver/interfaces/chargepoint/tests/test_chargepoint_driver.py index 61c7e70cc8..7851d675e4 100644 --- a/services/core/PlatformDriverAgent/platform_driver/interfaces/chargepoint/tests/test_chargepoint_driver.py +++ b/services/core/PlatformDriverAgent/platform_driver/interfaces/chargepoint/tests/test_chargepoint_driver.py @@ -42,6 +42,8 @@ from volttron.platform import get_services_core +pytestmark = [pytest.mark.contrib] + DRIVER1_CONFIG_STRING = """{ "driver_config": { "stationID" : "1:34003", diff --git a/services/core/PlatformDriverAgent/tests/test_eagle.py b/services/core/PlatformDriverAgent/tests/test_eagle.py index 9dccc6d436..8856236739 100644 --- a/services/core/PlatformDriverAgent/tests/test_eagle.py +++ b/services/core/PlatformDriverAgent/tests/test_eagle.py @@ -45,6 +45,8 @@ from volttrontesting.utils.utils import get_rand_http_address from volttron.platform.agent.known_identities import CONFIGURATION_STORE, PLATFORM_DRIVER +pytestmark = [pytest.mark.skip(reason='Community-contributed driver'), pytest.mark.contrib] + server_addr = get_rand_http_address() no_scheme = server_addr[7:] ip, port = no_scheme.split(':') diff --git a/services/core/PlatformDriverAgent/tests/test_global_override.py b/services/core/PlatformDriverAgent/tests/test_global_override.py index 3e88b0bf44..486ddeada9 100644 --- a/services/core/PlatformDriverAgent/tests/test_global_override.py +++ b/services/core/PlatformDriverAgent/tests/test_global_override.py @@ -85,6 +85,7 @@ def config_store_connection(request, volttron_instance): capabilities = [{'edit_config_store': {'identity': PLATFORM_DRIVER}}] connection = volttron_instance.build_connection(peer=CONFIGURATION_STORE, capabilities=capabilities) + gevent.sleep(1) # Reset platform driver config store connection.call("manage_delete_store", PLATFORM_DRIVER) diff --git a/services/core/PlatformDriverAgent/tests/test_global_settings.py b/services/core/PlatformDriverAgent/tests/test_global_settings.py index aa116c8998..b6b3d0ad0a 100644 --- a/services/core/PlatformDriverAgent/tests/test_global_settings.py +++ b/services/core/PlatformDriverAgent/tests/test_global_settings.py @@ -152,6 +152,7 @@ def cleanup(): def config_store_connection(request, volttron_instance): capabilities = [{'edit_config_store': {'identity': PLATFORM_DRIVER}}] connection = volttron_instance.build_connection(peer=CONFIGURATION_STORE, capabilities=capabilities) + gevent.sleep(1) # Reset platform driver config store connection.call("manage_delete_store", PLATFORM_DRIVER) From 9b9afa127079ef318d4380b9f8f19cdb5a1e2817 Mon Sep 17 00:00:00 2001 From: sgilbride Date: Tue, 8 Jun 2021 18:43:44 -0700 Subject: [PATCH 17/46] Hotfix to properly set Bearer to contain the access token. Added minor check for expired signature. --- volttron/platform/web/platform_web_service.py | 5 +++++ volttron/platform/web/templates/login.html | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/volttron/platform/web/platform_web_service.py b/volttron/platform/web/platform_web_service.py index 04cea7716a..62a6d6fddd 100644 --- a/volttron/platform/web/platform_web_service.py +++ b/volttron/platform/web/platform_web_service.py @@ -48,6 +48,7 @@ import gevent import gevent.pywsgi +import jwt from cryptography.hazmat.primitives import serialization from gevent import Greenlet from jinja2 import Environment, FileSystemLoader, select_autoescape @@ -736,6 +737,10 @@ def jsonrpc_verify_and_dispatch(self, authentication): except NotAuthorized: _log.error("Unauthorized user attempted to connect to platform.") return False + except jwt.ExpiredSignatureError: + _log.error("User attempted to connect with an expired signature.") + return False + return True diff --git a/volttron/platform/web/templates/login.html b/volttron/platform/web/templates/login.html index 1607c118df..21c0eecfa1 100644 --- a/volttron/platform/web/templates/login.html +++ b/volttron/platform/web/templates/login.html @@ -39,12 +39,12 @@ }, 200: function(response){ if(location.protocol != 'https:'){ - console.log("HTTP") - document.cookie = "Bearer="+response.responseText+"; path=/"; + console.log("HTTP"); + document.cookie = "Bearer="+response.access_token+"; path=/"; } else{ - console.log("HTTPS") - document.cookie = "Bearer="+response.responseText+"; secure=True; path=/"; + console.log("HTTPS"); + document.cookie = "Bearer="+response.access_token+"; secure=True; path=/"; } window.location=window.location.origin + "/admin/pending_auth_reqs.html"; From 3cc84d0450781a6e2f2d9ca9947fe7363fa6628e Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:43:10 -0700 Subject: [PATCH 18/46] Update login.html Removed alert content when successful resonse --- volttron/platform/web/templates/login.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/volttron/platform/web/templates/login.html b/volttron/platform/web/templates/login.html index 21c0eecfa1..bedb90e5ec 100644 --- a/volttron/platform/web/templates/login.html +++ b/volttron/platform/web/templates/login.html @@ -26,11 +26,9 @@ 'Accept': 'application/json' }, success: function(content){ - alert(content); $("#response").append(content) }, failure: function(content){ - alert("failed!"); $('#response').append("FAILURE "+ content.toString()); }, statusCode: { From f3d563ac36839c5d9b2df60a9017418e5dd54052 Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Mon, 14 Jun 2021 14:05:48 -0700 Subject: [PATCH 19/46] Delete build-dependency-cache.yml Remove the dependency cache as it is causing build errors. --- .github/workflows/build-dependency-cache.yml | 65 -------------------- 1 file changed, 65 deletions(-) delete mode 100644 .github/workflows/build-dependency-cache.yml diff --git a/.github/workflows/build-dependency-cache.yml b/.github/workflows/build-dependency-cache.yml deleted file mode 100644 index b1498d894f..0000000000 --- a/.github/workflows/build-dependency-cache.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- -# Creates a cache of the env directory -# Documentation for the syntax of this file is located -# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions - -# The workflow name will show up in the action tab on github during execution -# https://github.com/VOLTTRON/volttron/actions (or if you are pushing to your own fork change the user) - -name: cache virtual env - -# Check the cache when a push happens to the repository. -on: [push] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04] - python-version: [3.6, 3.7, 3.8] - steps: - # checkout the volttron repository and set current direectory to it - - uses: actions/checkout@v2 - - - name: Set up Python ${{matrix.os}} ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - id: envcache - uses: actions/cache@v2 - env: - cache-name: cache-env - with: - # This path is specific to Ubuntu - path: ./env - # Look to see if there is a cache hit for the corresponding requirements file - key: env-${{ matrix.os }}-${{matrix.python-version}}-${{hashFiles('requirements.py')}} - #-${{hashFiles('requirements.py')}} - # env-${{ matrix.os }}-${{matrix.python-version}} - - restore-keys: | - env-${{ matrix.os }}-${{matrix.python-version}}-${{hashFiles('requirements.py')}} - env-${{ matrix.os }}-${{matrix.python-version}} - - - name: Check env existance - id: check_files - uses: andstor/file-existence-action@v1 - with: - files: "env/bin/activate" - - - name: Install dependencies - if: steps.check_files.outputs.files_exists != 'true' - run: | - pip install wheel - python bootstrap.py --all --force - -# Only works on default branch of the target repo -# - name: Repository Dispatch -# uses: peter-evans/repository-dispatch@v1 -# with: -# token: ${{ secrets.WORKFLOW_ACCESS_TOKEN }} -# repository: ${{ github.repository }} -# event-type: my-event -# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' From 4d480c4d8a1ce7111e3974d308203b0365d43307 Mon Sep 17 00:00:00 2001 From: shwetha niddodi Date: Mon, 14 Jun 2021 16:25:41 -0700 Subject: [PATCH 20/46] Moving agents that are unsupported, cntributed and deprecated into respective folders so that the users are aware of the state of the agents --- deprecated/FailoverAgent/README.md | 68 + deprecated/FailoverAgent/config | 13 + deprecated/FailoverAgent/conftest.py | 6 + deprecated/FailoverAgent/failover/__init__.py | 0 deprecated/FailoverAgent/failover/agent.py | 301 + deprecated/FailoverAgent/setup.py | 72 + .../FailoverAgent/tests/test_failover.py | 206 + .../tests/test_simple_failover.py | 198 + services/contrib/MarketServiceAgent/IDENTITY | 1 + services/contrib/MarketServiceAgent/README.md | 30 + .../base_market_agent/__init__.py | 175 + .../base_market_agent/buy_sell.py | 40 + .../base_market_agent/error_codes.py | 42 + .../base_market_agent/market_registration.py | 140 + .../base_market_agent/offer.py | 56 + .../base_market_agent/point.py | 100 + .../base_market_agent/poly_line.py | 365 + .../base_market_agent/poly_line_factory.py | 141 + .../base_market_agent/registration_manager.py | 122 + .../base_market_agent/rpc_proxy.py | 113 + services/contrib/MarketServiceAgent/config | 6 + .../contrib/MarketServiceAgent/conftest.py | 6 + .../market_service/__init__.py | 0 .../market_service/agent.py | 243 + .../market_service/director.py | 68 + .../market_service/market.py | 253 + .../market_service/market_list.py | 111 + .../market_service/market_participant.py | 54 + .../market_service/market_state.py | 46 + .../market_service/offer_manager.py | 103 + .../market_service/reservation_manager.py | 99 + .../MarketServiceAgent/requirements.txt | 2 + services/contrib/MarketServiceAgent/setup.py | 72 + .../MarketServiceAgent/tests/test_market.py | 88 + .../tests/test_market_list.py | 129 + .../tests/test_market_service_agent.py | 77 + .../MarketServiceAgent/tests/test_offer.py | 67 + .../unsupported/MongodbHistorian/README.md | 174 + services/unsupported/MongodbHistorian/config | 19 + .../unsupported/MongodbHistorian/conftest.py | 10 + .../MongodbHistorian/mongodb/__init__.py | 0 .../MongodbHistorian/mongodb/historian.py | 1119 + .../MongodbHistorian/requirements.txt | 3 + .../scripts/count_data_by_topic_pattern.py | 25 + .../scripts/rollup_data_by_time.py | 428 + .../scripts/rollup_data_using_groupby.py | 151 + .../unsupported/MongodbHistorian/setup.py | 72 + .../MongodbHistorian/tests/fixtures.py | 39 + .../MongodbHistorian/tests/mongod.conf | 43 + .../MongodbHistorian/tests/mongosetup.sh | 27 + .../MongodbHistorian/tests/purgemongo.sh | 3 + .../tests/test_mongohistorian.py | 1506 + .../tests/test_prod_query_mongo.py | 355 + services/unsupported/OpenADRVenAgent/IDENTITY | 1 + .../unsupported/OpenADRVenAgent/README.md | 120 + .../unsupported/OpenADRVenAgent/__init__.py | 0 .../certs/TEST_OpenADR_RSA_BOTH0002_Cert.pem | 65 + .../certs/TEST_OpenADR_RSA_MCA0002_Cert.pem | 32 + .../certs/TEST_OpenADR_RSA_RCA0002_Cert.pem | 33 + .../certs/TEST_RSA_VEN_171024145702_cert.pem | 25 + .../TEST_RSA_VEN_171024145702_privkey.pem | 28 + services/unsupported/OpenADRVenAgent/config | 81 + .../unsupported/OpenADRVenAgent/conftest.py | 6 + .../OpenADRVenAgent/install-ven-agent.sh | 8 + .../OpenADRVenAgent/openadrven/__init__.py | 0 .../OpenADRVenAgent/openadrven/agent.py | 1766 ++ .../OpenADRVenAgent/openadrven/models.py | 347 + .../OpenADRVenAgent/openadrven/oadr_20b.py | 22878 +++++++++++++++ .../openadrven/oadr_builder.py | 389 + .../OpenADRVenAgent/openadrven/oadr_common.py | 35 + .../openadrven/oadr_extractor.py | 341 + .../OpenADRVenAgent/requirements.txt | 4 + services/unsupported/OpenADRVenAgent/setup.py | 72 + .../test/ControlAgentSim/IDENTITY | 1 + .../test/ControlAgentSim/__init__.py | 0 .../ControlAgentSim/controlagentsim.config | 7 + .../controlagentsim/__init__.py | 0 .../ControlAgentSim/controlagentsim/agent.py | 286 + .../ControlAgentSim/install-control-agent.sh | 8 + .../test/ControlAgentSim/setup.py | 34 + .../OpenADRVenAgent/test/__init__.py | 0 .../OpenADRVenAgent/test/crypto_experiment.py | 92 + .../OpenADRVenAgent/test/curl_event.sh | 5 + .../OpenADRVenAgent/test/curl_report.sh | 5 + .../OpenADRVenAgent/test/curl_vtn_poll.sh | 5 + .../dkuhlman-generateds-cba0ef052d1e.zip | Bin 0 -> 923534 bytes .../oadr20b_schema/generateDS-2.29.0.tar.gz | Bin 0 -> 702960 bytes .../generateDS-2.29.3-old.tar.gz | Bin 0 -> 847425 bytes .../oadr20b_schema/generateDS-2.29.3.tar.gz | Bin 0 -> 847425 bytes .../oadr20b_schema/generateDS-2.29.7.tar.gz | Bin 0 -> 711607 bytes .../generateDS-2.29.7_unpatched.tar.gz | Bin 0 -> 711592 bytes .../test/oadr20b_schema/oadr_20b.xsd | 966 + .../oadr_20b_v2_29_0_original.py | 22867 +++++++++++++++ .../oadr_20b_v2_29_3_original.py | 22867 +++++++++++++++ .../oadr_20b_v2_29_4_original.py | 22881 ++++++++++++++++ ...adr_ISO_ISO3AlphaCurrencyCode_20100407.xsd | 223 + .../test/oadr20b_schema/oadr_atom.xsd | 238 + .../test/oadr20b_schema/oadr_ei_20b.xsd | 1012 + .../test/oadr20b_schema/oadr_emix_20b.xsd | 33 + .../test/oadr20b_schema/oadr_gml_20b.xsd | 42 + .../test/oadr20b_schema/oadr_greenbutton.xsd | 3954 +++ .../test/oadr20b_schema/oadr_power_20b.xsd | 260 + .../test/oadr20b_schema/oadr_pyld_20b.xsd | 52 + .../test/oadr20b_schema/oadr_siscale_20b.xsd | 106 + .../test/oadr20b_schema/oadr_strm_20b.xsd | 36 + .../test/oadr20b_schema/oadr_xcal_20b.xsd | 129 + .../test/oadr20b_schema/oadr_xml.xsd | 108 + .../oadr_xmldsig-properties-schema.xsd | 24 + .../test/oadr20b_schema/oadr_xmldsig.xsd | 239 + .../test/oadr20b_schema/oadr_xmldsig11.xsd | 120 + .../OpenADRVenAgent/test/test_ven.py | 315 + .../test/xml/crypto_experiment.xml | 5 + .../test/xml/epri_vtn_distribute_event.xml | 306 + .../xml/epri_vtn_response_not_registered.xml | 12 + .../test/xml/epri_vtn_response_valid.xml | 13 + .../test/xml/kisensum_vtn_cancel_event.xml | 56 + .../xml/kisensum_vtn_distribute_event.xml | 98 + .../xml/kisensum_vtn_registered_report.xml | 26 + .../test/xml/kisensum_vtn_response.xml | 13 + .../test/xml/sample_ven_poll_signed.xml | 30 + .../test/xml/test_vtn_cancel_event.xml | 56 + .../test/xml/test_vtn_distribute_event.xml | 56 + .../xml/test_vtn_distribute_event_no_end.xml | 56 + .../test_vtn_distribute_event_variable.xml | 56 + .../test/xml/test_vtn_registered_report.xml | 26 + .../test/xml/ven_created_event.xml | 24 + .../test/xml/ven_created_report.xml | 16 + .../OpenADRVenAgent/test/xml/ven_poll.xml | 8 + .../test/xml/ven_register_report.xml | 63 + .../test/xml/ven_update_report.xml | 43 + 130 files changed, 111696 insertions(+) create mode 100644 deprecated/FailoverAgent/README.md create mode 100644 deprecated/FailoverAgent/config create mode 100644 deprecated/FailoverAgent/conftest.py create mode 100644 deprecated/FailoverAgent/failover/__init__.py create mode 100644 deprecated/FailoverAgent/failover/agent.py create mode 100644 deprecated/FailoverAgent/setup.py create mode 100644 deprecated/FailoverAgent/tests/test_failover.py create mode 100644 deprecated/FailoverAgent/tests/test_simple_failover.py create mode 100644 services/contrib/MarketServiceAgent/IDENTITY create mode 100644 services/contrib/MarketServiceAgent/README.md create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/__init__.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/buy_sell.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/error_codes.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/market_registration.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/offer.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/point.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/poly_line.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/poly_line_factory.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/registration_manager.py create mode 100644 services/contrib/MarketServiceAgent/base_market_agent/rpc_proxy.py create mode 100644 services/contrib/MarketServiceAgent/config create mode 100644 services/contrib/MarketServiceAgent/conftest.py create mode 100644 services/contrib/MarketServiceAgent/market_service/__init__.py create mode 100644 services/contrib/MarketServiceAgent/market_service/agent.py create mode 100644 services/contrib/MarketServiceAgent/market_service/director.py create mode 100644 services/contrib/MarketServiceAgent/market_service/market.py create mode 100644 services/contrib/MarketServiceAgent/market_service/market_list.py create mode 100644 services/contrib/MarketServiceAgent/market_service/market_participant.py create mode 100644 services/contrib/MarketServiceAgent/market_service/market_state.py create mode 100644 services/contrib/MarketServiceAgent/market_service/offer_manager.py create mode 100644 services/contrib/MarketServiceAgent/market_service/reservation_manager.py create mode 100644 services/contrib/MarketServiceAgent/requirements.txt create mode 100644 services/contrib/MarketServiceAgent/setup.py create mode 100644 services/contrib/MarketServiceAgent/tests/test_market.py create mode 100644 services/contrib/MarketServiceAgent/tests/test_market_list.py create mode 100644 services/contrib/MarketServiceAgent/tests/test_market_service_agent.py create mode 100644 services/contrib/MarketServiceAgent/tests/test_offer.py create mode 100644 services/unsupported/MongodbHistorian/README.md create mode 100644 services/unsupported/MongodbHistorian/config create mode 100644 services/unsupported/MongodbHistorian/conftest.py create mode 100644 services/unsupported/MongodbHistorian/mongodb/__init__.py create mode 100644 services/unsupported/MongodbHistorian/mongodb/historian.py create mode 100644 services/unsupported/MongodbHistorian/requirements.txt create mode 100644 services/unsupported/MongodbHistorian/scripts/count_data_by_topic_pattern.py create mode 100644 services/unsupported/MongodbHistorian/scripts/rollup_data_by_time.py create mode 100644 services/unsupported/MongodbHistorian/scripts/rollup_data_using_groupby.py create mode 100644 services/unsupported/MongodbHistorian/setup.py create mode 100644 services/unsupported/MongodbHistorian/tests/fixtures.py create mode 100644 services/unsupported/MongodbHistorian/tests/mongod.conf create mode 100755 services/unsupported/MongodbHistorian/tests/mongosetup.sh create mode 100755 services/unsupported/MongodbHistorian/tests/purgemongo.sh create mode 100644 services/unsupported/MongodbHistorian/tests/test_mongohistorian.py create mode 100644 services/unsupported/MongodbHistorian/tests/test_prod_query_mongo.py create mode 100644 services/unsupported/OpenADRVenAgent/IDENTITY create mode 100644 services/unsupported/OpenADRVenAgent/README.md create mode 100644 services/unsupported/OpenADRVenAgent/__init__.py create mode 100644 services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_BOTH0002_Cert.pem create mode 100644 services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_MCA0002_Cert.pem create mode 100644 services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_RCA0002_Cert.pem create mode 100644 services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_cert.pem create mode 100644 services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_privkey.pem create mode 100644 services/unsupported/OpenADRVenAgent/config create mode 100644 services/unsupported/OpenADRVenAgent/conftest.py create mode 100644 services/unsupported/OpenADRVenAgent/install-ven-agent.sh create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/__init__.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/agent.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/models.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/oadr_20b.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/oadr_builder.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/oadr_common.py create mode 100644 services/unsupported/OpenADRVenAgent/openadrven/oadr_extractor.py create mode 100644 services/unsupported/OpenADRVenAgent/requirements.txt create mode 100644 services/unsupported/OpenADRVenAgent/setup.py create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/IDENTITY create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/__init__.py create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim.config create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/__init__.py create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/agent.py create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/install-control-agent.sh create mode 100644 services/unsupported/OpenADRVenAgent/test/ControlAgentSim/setup.py create mode 100644 services/unsupported/OpenADRVenAgent/test/__init__.py create mode 100644 services/unsupported/OpenADRVenAgent/test/crypto_experiment.py create mode 100644 services/unsupported/OpenADRVenAgent/test/curl_event.sh create mode 100644 services/unsupported/OpenADRVenAgent/test/curl_report.sh create mode 100644 services/unsupported/OpenADRVenAgent/test/curl_vtn_poll.sh create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/dkuhlman-generateds-cba0ef052d1e.zip create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/generateDS-2.29.0.tar.gz create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/generateDS-2.29.3-old.tar.gz create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/generateDS-2.29.3.tar.gz create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/generateDS-2.29.7.tar.gz create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/generateDS-2.29.7_unpatched.tar.gz create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_20b_v2_29_0_original.py create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_20b_v2_29_3_original.py create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_20b_v2_29_4_original.py create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_ISO_ISO3AlphaCurrencyCode_20100407.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_atom.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_ei_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_emix_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_gml_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_greenbutton.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_power_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_pyld_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_siscale_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_strm_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_xcal_20b.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_xml.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_xmldsig-properties-schema.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_xmldsig.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/oadr20b_schema/oadr_xmldsig11.xsd create mode 100644 services/unsupported/OpenADRVenAgent/test/test_ven.py create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/crypto_experiment.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/epri_vtn_distribute_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/epri_vtn_response_not_registered.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/epri_vtn_response_valid.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/kisensum_vtn_cancel_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/kisensum_vtn_distribute_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/kisensum_vtn_registered_report.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/kisensum_vtn_response.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/sample_ven_poll_signed.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/test_vtn_cancel_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/test_vtn_distribute_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/test_vtn_distribute_event_no_end.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/test_vtn_distribute_event_variable.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/test_vtn_registered_report.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/ven_created_event.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/ven_created_report.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/ven_poll.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/ven_register_report.xml create mode 100644 services/unsupported/OpenADRVenAgent/test/xml/ven_update_report.xml diff --git a/deprecated/FailoverAgent/README.md b/deprecated/FailoverAgent/README.md new file mode 100644 index 0000000000..418304ab64 --- /dev/null +++ b/deprecated/FailoverAgent/README.md @@ -0,0 +1,68 @@ +## Failover Agent + +The Failover agent provides a generic high availability option to VOLTTRON. When the primary platform becomes +inactive the secondary platform will start an installed agent. + + +### Standard Failover + +There are two behavior patterns implemented in the agent. In the default +configuration, the secondary instance will ask Volttron Central to verify +that the primary instance is down. This helps to avoid a split brain scenario. +If neither Volttron Central nor the other Failover instance is reachable +then the Failover agent will stop the agent it is managing. + +The following tables show the expected state of the configured agent on corresponding platform instance, given the state +of the agent on the other platform instance. + +**Behavior of Primary Instance** + +| | VC Up | VC Down | +|-----------------|-------|---------| +| Secondary Up | start | start | +| Secondary Down | start | stop | + +**Behavior of Secondary Instance** + +| | VC Up | VC Down | +|--------------|-----------------|---------| +| Primary Up | stop | stop | +| Primary Down | Verify with VC before starting | stop | + + +### Simple Failover + +There is also a *simple* configuration available that does not involve +coordination with Volttron Central. The secondary agent will start its managed +agent if believes the primary to be inactive. The simple primary always has its +managed agent started. + + +### Configuration + +Failover behavior is set in the failover agent's configuration file. Example +primary and secondary configuration files are shown below. + +``` +{ | { + "agent_id": "primary", | "agent_id": "secondary", + "simple_behavior": true, | "simple_behavior": true, + | + "remote_vip": "tcp://127.0.0.1:8001", | "remote_vip": "tcp://127.0.0.1:8000", + "remote_serverkey": "", | "remote_serverkey": "", + | + "agent_vip_identity": "platform.driver",| "agent_vip_identity": "platform.driver", + | + "heartbeat_period": 10, | "heartbeat_period": 10, + | + "timeout": 120 | "timeout": 120 +} | } +``` + +- **agent_id** - primary **or** secondary +- **simple_behavior** - Switch to turn on or off simple behavior. Both instances should match. +- **remote_vip** - Address where *remote_id* can be reached. +- **remote_serverkey** - The public key of the platform where *remote_id* lives. +- **agent_vip_identity** - The :term:`VIP Identity` of the agent that we want to manage. +- **heartbeat_period** - Send a message to *remote_id* with this period. Measured in seconds. +- **timeout** - Consider a platform inactive if a heartbeat has not been received for *timeout* seconds. diff --git a/deprecated/FailoverAgent/config b/deprecated/FailoverAgent/config new file mode 100644 index 0000000000..53332cf49b --- /dev/null +++ b/deprecated/FailoverAgent/config @@ -0,0 +1,13 @@ +{ + "agent_id": "primary", + "simple_behavior": true, + + "remote_vip": "tcp://127.0.0.1:8001", + "remote_serverkey": "", + + "agent_vip_identity": "platform.driver", + + "heartbeat_period": 10, + + "timeout": 120 +} diff --git a/deprecated/FailoverAgent/conftest.py b/deprecated/FailoverAgent/conftest.py new file mode 100644 index 0000000000..8559470457 --- /dev/null +++ b/deprecated/FailoverAgent/conftest.py @@ -0,0 +1,6 @@ +import sys + +from volttrontesting.fixtures.volttron_platform_fixtures import * + +# Add system path of the agent's directory +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) \ No newline at end of file diff --git a/deprecated/FailoverAgent/failover/__init__.py b/deprecated/FailoverAgent/failover/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deprecated/FailoverAgent/failover/agent.py b/deprecated/FailoverAgent/failover/agent.py new file mode 100644 index 0000000000..a5d233baf4 --- /dev/null +++ b/deprecated/FailoverAgent/failover/agent.py @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + + + +import datetime +import logging +import time + +from volttron.platform.agent import utils +from volttron.platform.agent.known_identities import CONTROL +from volttron.platform.jsonrpc import RemoteError +from volttron.platform.keystore import KnownHostsStore +from volttron.platform.messaging.health import Status, STATUS_BAD, STATUS_GOOD +from volttron.platform.vip.agent import Agent, Core, PubSub, Unreachable +from volttron.platform.vip.agent.connection import Connection +from volttron.platform.scheduling import periodic + +utils.setup_logging() +_log = logging.getLogger(__name__) +__version__ = '0.2' + + +class FailoverAgent(Agent): + + def __init__(self, config_path, **kwargs): + super(FailoverAgent, self).__init__(**kwargs) + config = utils.load_config(config_path) + + # Get agent and remote ids + agent_id = config["agent_id"] + if agent_id == "primary": + self.agent_id = "primary" + self.remote_id = "secondary" + elif agent_id == "secondary": + self.agent_id = "secondary" + self.remote_id = "primary" + else: + _log.error("agent_id must be either 'primary' or 'secondary'") + + # Modify ids if we're using the simple option + # Defaults to true pending vc coordination + use_simple = config.get("simple_behavior", True) + if use_simple: + self.agent_id = "simple_" + self.agent_id + self.remote_id = "simple_" + self.remote_id + + self.remote_vip = config["remote_vip"] + + hosts = KnownHostsStore() + self.remote_serverkey = hosts.serverkey(self.remote_vip) + if self.remote_serverkey is None: + self.remote_serverkey = config["remote_serverkey"] + + self.agent_vip_identity = config["agent_vip_identity"] + self.heartbeat_period = config["heartbeat_period"] + self.timeout = config["timeout"] + + self.vc_timeout = self.timeout + self.remote_timeout = self.timeout + self.agent_uuid = None + self.heartbeat = None + self.last_connected = None + + self._state = True, True + self._state_machine = getattr(self, self.agent_id + '_state_machine') + + @Core.receiver("onstart") + def onstart(self, sender, **kwargs): + # Start an agent to send heartbeats to the other failover instance + self.heartbeat = self.build_connection() + + connected = self.heartbeat.is_connected() + _log.debug("is connected to remote instance: {}".format(connected)) + + def heartbeat(): + try: + self.heartbeat.publish('heartbeat/{}'.format(self.agent_id)) + self.last_connected = self.timestamp() + except Unreachable: + if self.timestamp() < self.last_connected + self.timeout: + _log.debug("Attempting reconnect to remote instance") + self.heartbeat.kill() + self.heartbeat = self.build_connection() + self.last_connected = self.timestamp() + + self.core.schedule(periodic(self.heartbeat_period), heartbeat) + self.core.schedule(periodic(1), self.check_pulse) + + def timestamp(self): + return time.mktime(datetime.datetime.now().timetuple()) + + def build_connection(self): + return Connection(self.remote_vip, + peer='', + serverkey=self.remote_serverkey, + publickey=self.core.publickey, + secretkey=self.core.secretkey) + + @PubSub.subscribe('pubsub', 'heartbeat') + def on_match(self, peer, sender, bus, topic, headers, message): + if topic.startswith('heartbeat/VolttronCentralAgent'): + self.vc_timeout = self.timeout + elif topic.startswith('heartbeat/' + self.remote_id): + self.remote_timeout = self.timeout + + def _find_agent_uuid(self): + self.agent_uuid = None + agents = self.vip.rpc.call(CONTROL, 'list_agents').get() + uuids = [a['uuid'] for a in agents] + for uuid in uuids: + vip_id = self.vip.rpc.call(CONTROL, + 'agent_vip_identity', + uuid).get() + + if vip_id == self.agent_vip_identity: + self.agent_uuid = uuid + + if self.agent_uuid is None: + _log.error("Agent {} is not installed" + .format(self.agent_vip_identity)) + + def check_pulse(self): + self.vc_timeout -= 1 + self.remote_timeout -= 1 + + vc_is_up = self.vc_timeout > 0 + remote_is_up = self.remote_timeout > 0 + current_state = remote_is_up, vc_is_up + + self._find_agent_uuid() + if self.agent_uuid is None: + return + + self._state_machine(current_state) + + def _agent_control(self, command): + try: + self.vip.rpc.call(CONTROL, command, self.agent_uuid).get() + except RemoteError as e: + _log.error("Error calling {} on control".format(command)) + + def primary_state_machine(self, current_state): + """Function representing the state machine for a primary + instace. + + Start the target agent if either the secondary instance or + Volttron Central are active. Otherwise stop the target agent. + + :param current_state: Indicates if remote platforms are active. + :type current_state: tuple of booleans + """ + raise NotImplementedError("Coordination with VC not implemeted") + + secondary_is_up, vc_is_up = current_state + if secondary_is_up or vc_is_up: + self._agent_control('start_agent') + else: + self._agent_control('stop_agent') + + def secondary_state_machine(self, current_state): + """Function representing the state machine for a secondary + instance. + + If this agent stops getting heartbeats from the primary, it will + ask Volttron Central for verification that the primary is inactive + before starting the target agent. + + The target agent will be stopped if both the primary instance + and Volttron Central are not communicating. + + :param current_state: Indicates if remote platforms are active. + :type current_state: tuple of booleans + """ + raise NotImplementedError("Coordination with VC not implemeted") + + primary_is_up, vc_is_up = current_state + if not primary_is_up and vc_is_up: + pass # verify and start master + else: + self._agent_control('stop_agent') + + def simple_primary_state_machine(self, current_state): + """Function representing the state machine for a simple primary + instance. Always tries to start the target agent. + + :param current_state: Indicates if remote platforms are active. Ingored. + :type current_state: tuple of booleans + """ + alert_key = 'failover {}'.format(self.agent_id) + if current_state != self._state: + context = 'Starting agent {}'.format(self.agent_vip_identity) + self._state = current_state + _log.warning(context) + status = Status.build(STATUS_GOOD, context=context) + self.vip.health.send_alert(alert_key, status) + + proc_info = self.vip.rpc.call(CONTROL, + 'agent_status', + self.agent_uuid).get() + + is_not_running = proc_info[0] is None and proc_info[1] is None + if is_not_running: + self._agent_control('start_agent') + + def simple_secondary_state_machine(self, current_state): + """Function representing the state machine for a simple secondary + instance. Starts the target agent if the simple primary is not + communicating. + + :param current_state: Indicates if remote platforms are + active. Ignores the Volttron Central status. + :type current_state: tuple of booleans + """ + primary_is_up, _ = current_state + + alert_key = 'failover {}'.format(self.agent_id) + + if primary_is_up: + context = 'Primary is active stopping agent {}'.format( + self.agent_vip_identity) + if current_state != self._state: + self._state = current_state + _log.warning(context) + status = Status.build(STATUS_GOOD, context=context) + self.vip.health.send_alert(alert_key, status) + + self._agent_control('stop_agent') + + else: + context = 'Primary is inactive starting agent {}'.format( + self.agent_vip_identity) + if current_state != self._state: + self._state = current_state + _log.warning(context) + status = Status.build(STATUS_BAD, context=context) + self.vip.health.send_alert(alert_key, status) + + agents = self.vip.rpc.call(CONTROL, 'list_agents').get() + _log.info(f"simple_secondary_state_machine List agents: {self.agent_uuid}, {agents}") + + agents_stats = self.vip.rpc.call(CONTROL, 'status_agents').get() + _log.info(f"simple_secondary_state_machine Agent stats: {self.agent_uuid}, {agents_stats}") + + proc_info = self.vip.rpc.call(CONTROL, + 'agent_status', + self.agent_uuid).get() + + _log.info(f"simple_secondary_state_machine: {self.agent_uuid}, {proc_info}") + + is_not_running = proc_info[0] is None and proc_info[1] is None + + if is_not_running: + _log.info(f"simple_secondary_state_machine, starting agent: {self.agent_uuid}") + self._agent_control('start_agent') + + +def main(): + try: + utils.vip_main(FailoverAgent, version=__version__) + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/deprecated/FailoverAgent/setup.py b/deprecated/FailoverAgent/setup.py new file mode 100644 index 0000000000..cc64769bff --- /dev/null +++ b/deprecated/FailoverAgent/setup.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from os import path +from setuptools import setup, find_packages + +MAIN_MODULE = 'agent' + +# Find the agent package that contains the main module +packages = find_packages('.') +agent_package = '' +for package in find_packages(): + # Because there could be other packages such as tests + if path.isfile(package + '/' + MAIN_MODULE + '.py') is True: + agent_package = package +if not agent_package: + raise RuntimeError('None of the packages under {dir} contain the file ' + '{main_module}'.format(main_module=MAIN_MODULE + '.py', + dir=path.abspath('.'))) + +# Find the version number from the main module +agent_module = agent_package + '.' + MAIN_MODULE +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) +__version__ = _temp.__version__ + +# Setup +setup( + name=agent_package + 'agent', + version=__version__, + install_requires=['volttron'], + packages=packages, + entry_points={ + 'setuptools.installation': [ + 'eggsecutable = ' + agent_module + ':main', + ] + } +) diff --git a/deprecated/FailoverAgent/tests/test_failover.py b/deprecated/FailoverAgent/tests/test_failover.py new file mode 100644 index 0000000000..cf3460022c --- /dev/null +++ b/deprecated/FailoverAgent/tests/test_failover.py @@ -0,0 +1,206 @@ +import tempfile + +from contextlib import contextmanager + +import pytest +import gevent + +from volttron.platform import get_services_core, get_examples, get_ops +from volttron.platform.keystore import KeyStore + +primary_config = { + "agent_id": "primary", + "remote_id": "secondary", + # "remote_vip": "", + "agent_vip_identity": "listener", + "heartbeat_period": 1, + "timeout": 3 +} + +secondary_config = { + "agent_id": "secondary", + "remote_id": "primary", + # "remote_vip": "", + "agent_vip_identity": "listener", + "heartbeat_period": 1, + "timeout": 3 +} + +SLEEP_TIME = 5 + +primary_failover = None +secondary_failover = None +vc_uuid = None + + +def tcp_to(instance): + + tmp = tempfile.NamedTemporaryFile() + key = KeyStore(tmp.name) + key.generate() + + return "{}?serverkey={}&publickey={}&secretkey={}".format( + instance.vip_address, + instance.serverkey, + key.public, + key.secret) + + +def all_agents_running(instance): + agents = instance.list_agents() + uuids = [a['uuid'] for a in agents] + return all([instance.is_agent_running(uuid) for uuid in uuids]) + + +@pytest.fixture(scope="module") +def failover(request, get_volttron_instances): + global primary_config + global secondary_config + global primary_failover + global secondary_failover + global vc_uuid + + pytest.skip("Coordination with VC not implemted") + + primary, secondary, vc = get_volttron_instances(3) + primary.allow_all_connections() + secondary.allow_all_connections() + vc.allow_all_connections() + + # configure vc + vc_uuid = vc.install_agent( + agent_dir=get_services_core("VolttronCentralPlatform")) + + # configure primary + primary_platform = primary.install_agent( + agent_dir=get_services_core("VolttronCentralPlatform")) + # register with vc + primary_listener = primary.install_agent(agent_dir=get_examples("ListenerAgent"), + vip_identity="listener", + start=False) + + primary_config["remote_vip"] = tcp_to(secondary) + primary_failover = primary.install_agent(agent_dir=get_ops("FailoverAgent"), + config_file=primary_config) + + # configure secondary + secondary_platform = secondary.install_agent(agent_dir=get_services_core("VolttronCentralPlatform")) + # register with vc + secondary_listener = secondary.install_agent(agent_dir=get_examples("ListenerAgent"), + vip_identity="listener", + start=False) + + secondary_config["remote_vip"] = tcp_to(primary) + secondary_failover = secondary.install_agent(agent_dir=get_ops("FailoverAgent"), + config_file=secondary_config) + + def stop(): + vc.stop_agent(vc_uuid) + vc.shutdown_platform() + + primary.stop_agent(primary_failover) + primary.stop_agent(primary_platform) + primary.stop_agent(primary_listener) + primary.shutdown_platform() + + secondary.stop_agent(secondary_failover) + secondary.stop_agent(secondary_platform) + secondary.stop_agent(secondary_listener) + secondary.shutdown_platform() + + request.addfinalizer(stop) + + return primary, secondary, vc + + +def baseline_check(vc, primary, secondary): + assert all_agents_running(vc) + assert all_agents_running(primary) + assert not all_agents_running(secondary) + + +@contextmanager +def stop_start(platform, uuid): + platform.stop_agent(uuid) + gevent.sleep(1) + yield + platform.start_agent(uuid) + gevent.sleep(1) + + +@pytest.mark.xfail(reason='Coordination with VC not implemted') +def test_vc_death_behavior(failover): + global vc_uuid + primary, secondary, vc = failover + + baseline_check(vc, primary, secondary) + + with stop_start(vc, vc_uuid): + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + assert not all_agents_running(secondary) + + baseline_check(vc, primary, secondary) + + +@pytest.mark.xfail(reason='Coordination with VC not implemted') +def test_primary_death_behavior(failover): + global primary_failover + primary, secondary, vc = failover + + baseline_check(vc, primary, secondary) + + with stop_start(primary, primary_failover): + gevent.sleep(SLEEP_TIME) + assert all_agents_running(vc) + assert all_agents_running(secondary) + + baseline_check(vc, primary, secondary) + + +@pytest.mark.xfail(reason='Coordination with VC not implemted') +def test_secondary_death_behavior(failover): + global secondary_failover + primary, secondary, vc = failover + + baseline_check(vc, primary, secondary) + + with stop_start(secondary, secondary_failover): + gevent.sleep(SLEEP_TIME) + assert all_agents_running(vc) + assert all_agents_running(primary) + + baseline_check(vc, primary, secondary) + + +@pytest.mark.xfail(reason='Coordination with VC not implemted') +def test_primary_when_others_dead(failover): + global vc_uuid + global secondary_failover + primary, secondary, vc = failover + + baseline_check(vc, primary, secondary) + + with stop_start(vc, vc_uuid), stop_start(secondary, secondary_failover): + gevent.sleep(SLEEP_TIME) + assert not all_agents_running(vc) + assert not all_agents_running(primary) + assert not all_agents_running(secondary) + + baseline_check(vc, primary, secondary) + + +@pytest.mark.xfail(reason='Coordination with VC not implemted') +def test_secondary_when_others_dead(failover): + global vc_uuid + global primary_failover + primary, secondary, vc = failover + + baseline_check(vc, primary, secondary) + with stop_start(vc, vc_uuid), stop_start(primary, primary_failover): + gevent.sleep(SLEEP_TIME) + assert not all_agents_running(vc) + assert not all_agents_running(primary) + assert not all_agents_running(secondary) + + baseline_check(vc, primary, secondary) diff --git a/deprecated/FailoverAgent/tests/test_simple_failover.py b/deprecated/FailoverAgent/tests/test_simple_failover.py new file mode 100644 index 0000000000..0100230d19 --- /dev/null +++ b/deprecated/FailoverAgent/tests/test_simple_failover.py @@ -0,0 +1,198 @@ +import tempfile + +import pytest +import gevent + +from volttron.platform import get_examples, get_ops, jsonapi +from volttrontesting.utils.agent_additions import add_listener + +simple_primary_config = { + "agent_id": "primary", + "simple_behavior": True, + # "remote_vip": "", + # "remote_serverkey":"", + "agent_vip_identity": "listener", + "heartbeat_period": 1, + "timeout": 2 +} + +simple_secondary_config = { + "agent_id": "secondary", + "simple_behavior": True, + # "remote_vip": "", + # "remote_serverkey": "", + "agent_vip_identity": "listener", + "heartbeat_period": 1, + "timeout": 2 +} + +SLEEP_TIME = 3 + +uuid_primary = None +uuid_secondary = None +listener_primary = None +listener_secondary = None + +def all_agents_running(instance): + agents = instance.list_agents() + uuids = [a['uuid'] for a in agents] + return all([instance.is_agent_running(uuid) for uuid in uuids]) + + +@pytest.fixture(scope="module") +def simple_failover(request, get_volttron_instances): + global simple_primary_config + global simple_secondary_config + global uuid_primary + global uuid_secondary + global listener_primary + global listener_secondary + + + primary, secondary = get_volttron_instances(2) + + if primary.messagebus != 'zmq': + pytest.skip("Failover only valid for zmq instances.") + return + + primary.allow_all_connections() + secondary.allow_all_connections() + + # configure primary + listener_primary = add_listener(primary, start=True, vip_identity="listener") + # primary.install_agent(agent_dir=get_examples("ListenerAgent"), + # vip_identity="listener", + # start=False) + + simple_primary_config["remote_vip"] = secondary.vip_address + simple_primary_config["remote_serverkey"] = secondary.serverkey + uuid_primary = primary.install_agent(agent_dir=get_ops("FailoverAgent"), + config_file=simple_primary_config) + + # configure secondary + listener_secondary = add_listener(secondary, start=False, vip_identity="listener") + # listener_secondary = secondary.install_agent(agent_dir=get_examples("ListenerAgent"), + # vip_identity="listener", + # start=False) + + simple_secondary_config["remote_vip"] = primary.vip_address + simple_secondary_config["remote_serverkey"] = primary.serverkey + uuid_secondary = secondary.install_agent(agent_dir=get_ops("FailoverAgent"), + config_file=simple_secondary_config) + + gevent.sleep(SLEEP_TIME) + + assert all_agents_running(primary) + assert not all_agents_running(secondary) + assert not secondary.is_agent_running(listener_secondary) + + def cleanup(): + primary.stop_agent(uuid_primary) + primary.stop_agent(listener_primary) + primary.shutdown_platform() + + secondary.stop_agent(uuid_secondary) + secondary.stop_agent(listener_secondary) + secondary.shutdown_platform() + + request.addfinalizer(cleanup) + return primary, secondary + + +def test_simple_failover(simple_failover): + global uuid_primary + global listener_secondary + alert_messages = {} + + primary, secondary = simple_failover + + # Listen for alerts from state changes + def onmessage(peer, sender, bus, topic, headers, message): + alert = jsonapi.loads(message)["context"] + + try: + alert_messages[alert] += 1 + except KeyError: + alert_messages[alert] = 1 + + assert not secondary.is_agent_running(listener_secondary) + listen1 = primary.build_agent() + listen1.vip.pubsub.subscribe(peer='pubsub', + prefix='alert', + callback=onmessage).get() + + listen2 = secondary.build_agent() + listen2.vip.pubsub.subscribe(peer='pubsub', + prefix='alert', + callback=onmessage).get() + + assert not secondary.is_agent_running(listener_secondary) + # make sure the secondary will take over + primary.stop_agent(uuid_primary) + gevent.sleep(SLEEP_TIME) + assert not all_agents_running(primary) + assert all_agents_running(secondary) + assert 'Primary is inactive starting agent listener' in alert_messages + + # secondary should stop its listener + primary.start_agent(uuid_primary) + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + assert not all_agents_running(secondary) + assert 'Primary is active stopping agent listener' in alert_messages + assert 'Starting agent listener' in alert_messages + listen1.core.stop() + listen2.core.stop() + + +def test_primary_on_secondary_crash(simple_failover): + global uuid_secondary + primary, secondary = simple_failover + + secondary.skip_cleanup = True + secondary.shutdown_platform() + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + + secondary.startup_platform(secondary.vip_address) + secondary.start_agent(uuid_secondary) + + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + assert not all_agents_running(secondary) + secondary.skip_cleanup = False + + +def test_secondary_on_primary_crash(simple_failover): + global uuid_primary + primary, secondary = simple_failover + + primary.skip_cleanup = True + primary.shutdown_platform() + gevent.sleep(SLEEP_TIME) + assert all_agents_running(secondary) + + primary.startup_platform(primary.vip_address) + + # primary.startup_platform(vip_address, **args) + primary.start_agent(uuid_primary) + + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + assert not all_agents_running(secondary) + + primary.skip_cleanup = False + + +def test_can_handle_agent_upgrade(simple_failover): + global listener_primary + primary, secondary = simple_failover + + primary.remove_agent(listener_primary) + listener_primary = primary.install_agent(agent_dir=get_examples("ListenerAgent"), + vip_identity="listener", + start=False) + + gevent.sleep(SLEEP_TIME) + assert all_agents_running(primary) + assert not all_agents_running(secondary) diff --git a/services/contrib/MarketServiceAgent/IDENTITY b/services/contrib/MarketServiceAgent/IDENTITY new file mode 100644 index 0000000000..ec69effe39 --- /dev/null +++ b/services/contrib/MarketServiceAgent/IDENTITY @@ -0,0 +1 @@ +platform.market \ No newline at end of file diff --git a/services/contrib/MarketServiceAgent/README.md b/services/contrib/MarketServiceAgent/README.md new file mode 100644 index 0000000000..7255d7735f --- /dev/null +++ b/services/contrib/MarketServiceAgent/README.md @@ -0,0 +1,30 @@ +# Market Service Agent + +The Market Service Agent is used to allow agents to use transactive markets +to implement transactive control strategies. The Market Service Agent provides +an implementation of double blind auction markets that can be used by multiple agents. + +Agents that want to use the Market Service Agent inherit from the :ref:`base MarketAgent`. +The base MarketAgent handles all of the communication between the agent and the MarketServiceAgent. + +## Configuration + +1. "market_period" - The time allowed for a market cycle in seconds. After this amount of time the market starts again. + Defaults to 300. +2. "reservation_delay" - The time delay between the start of a market cycle and the start of gathering market + reservations in seconds. Defaults to 0. +3. "offer_delay" - The time delay between the start of gathering market reservations and the start of gathering market + bids/offers in seconds. Defaults to 120. +4. "verbose_logging" - If True this enables verbose logging. If False, there is little or no logging. Defaults to True. + + +## Sample configuration file + +``` {.python} + { + "market_period": 300, + "reservation_delay": 0, + "offer_delay": 120, + "verbose_logging": True + } +``` diff --git a/services/contrib/MarketServiceAgent/base_market_agent/__init__.py b/services/contrib/MarketServiceAgent/base_market_agent/__init__.py new file mode 100644 index 0000000000..51c0d9b5f9 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/__init__.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging + +from volttron.platform.agent import utils +from volttron.platform.vip.agent import PubSub +from volttron.platform.vip.agent import Agent +from volttron.platform.messaging.topics import MARKET_RESERVE, MARKET_BID, MARKET_CLEAR, MARKET_AGGREGATE, MARKET_ERROR +from volttron.platform.agent.base_market_agent.registration_manager import RegistrationManager +from volttron.platform.agent.base_market_agent.poly_line_factory import PolyLineFactory +from volttron.platform.agent.base_market_agent.rpc_proxy import RpcProxy + +_log = logging.getLogger(__name__) +utils.setup_logging() +__version__ = "0.01" + + +class MarketAgent(Agent): + """ + The MarketAgents serves as the base class for any agent that wants to praticipate in + an auction market. By inheriting from this agent all the remote communication + with the MarketService is handled and the sub-class can be unconcerned with those details. + """ + def __init__(self, verbose_logging=True, timeout=60, **kwargs): + super(MarketAgent, self).__init__(**kwargs) + _log.debug("vip_identity: " + self.core.identity) + rpc_proxy = RpcProxy(self.vip.rpc.call, verbose_logging=verbose_logging, timeout=timeout) + self.registrations = RegistrationManager(rpc_proxy) + self.verbose_logging = verbose_logging + + @PubSub.subscribe('pubsub', MARKET_RESERVE) + def match_reservation(self, peer, sender, bus, topic, headers, message): + timestamp = utils.parse_timestamp_string(message[0]) + decoded_message = "Timestamp: {}".format(timestamp) + self.log_event("match_reservation", peer, sender, bus, topic, headers, decoded_message) + self.registrations.request_reservations(timestamp) + + + @PubSub.subscribe('pubsub', MARKET_BID) + def match_make_offer(self, peer, sender, bus, topic, headers, message): + timestamp = utils.parse_timestamp_string(message[0]) + unformed_markets = message[1] + decoded_message = "Timestamp: {}".format(timestamp) + self.log_event("match_make_offer", peer, sender, bus, topic, headers, decoded_message) + self.registrations.request_offers(timestamp, unformed_markets) + + @PubSub.subscribe('pubsub', MARKET_CLEAR) + def match_report_clear_price(self, peer, sender, bus, topic, headers, message): + timestamp = utils.parse_timestamp_string(message[0]) + market_name = message[1] + quantity = message[2] + price = message[3] + decoded_message = "Timestamp: {} Market: {} Price: {} Quantity: {}".format(timestamp, market_name, price, quantity) + self.log_event("match_report_clear_price", peer, sender, bus, topic, headers, decoded_message) + self.registrations.report_clear_price(timestamp, market_name, price, quantity) + + @PubSub.subscribe('pubsub', MARKET_AGGREGATE) + def match_report_aggregate(self, peer, sender, bus, topic, headers, message): + timestamp = utils.parse_timestamp_string(message[0]) + market_name = message[1] + buyer_seller = message[2] + aggregate_curve_points = message[3] + decoded_message = "Timestamp: {} Market: {} {} Curve: {}".format(timestamp, market_name, buyer_seller, aggregate_curve_points) + self.log_event("match_report_aggregate", peer, sender, bus, topic, headers, decoded_message) + aggregate_curve = PolyLineFactory.fromTupples(aggregate_curve_points) + self.registrations.report_aggregate(timestamp, market_name, buyer_seller, aggregate_curve) + + @PubSub.subscribe('pubsub', MARKET_ERROR) + def match_report_error(self, peer, sender, bus, topic, headers, message): + timestamp = utils.parse_timestamp_string(message[0]) + market_name = message[1] + error_code = message[2] + error_message = message[3] + aux = message[4] + decoded_message = "Timestamp: {} Market: {} Code: {} Message: {}".format(timestamp, market_name, error_code, error_message) + self.log_event("match_report_error", peer, sender, bus, topic, headers, decoded_message) + self.registrations.report_error(timestamp, market_name, error_code, error_message, aux) + + def log_event(self, method_name, peer, sender, bus, topic, headers, decoded_message): + if self.verbose_logging: + _log.debug("{} Peer: {} Sender: {} Bus: {} Topic: {} Headers: {} Message: {}".format(method_name, peer, sender, bus, topic, headers, decoded_message)) + + def join_market (self, market_name, buyer_seller, reservation_callback, + offer_callback, aggregate_callback, price_callback, error_callback): + """ + This routine is called once to join a market as a buyer or a seller. + The agent supplies call-back functions that the MarketAgents calls as the market process proceeds. + + :param market_name: The name of the market commodity. + + :param buyer_seller: A string indicating whether the agent is buying from or selling to the market. + The agent shall use the pre-defined strings provided. + + :param reservation_callback: This callback is called at the beginning of each round of bidding and clearing. + The agent can choose whether or not to participate in this round. + If the agent wants to participate it returns true otherwise it returns false. + If the agent does not specify a callback routine a reservation will be made for each round automatically. + A market will only exist if there are reservations for at least one buyer and at least one seller. + If the market fails to achieve the minimum participation the error callback will be called. + + :param offer_callback: If the agent has made a reservation for the market this routine is called. + If the agent wishes to make an offer at this time the market agent computes either supply or demand curves + as appropriate and offers them to the market service by calling the make offer method. + For each market joined either an offer callback or an aggregate callback is required. + You can’t supply both for any single market. + + :param aggregate_callback: When a market has received all its buy offers it calculates an aggregate + demand curve. When the market receives all of its sell offers it calculates an aggregate supply curve. + This callback delivers the aggregate curve to the market agent whenever the appropriate curve + becomes available. If the market agent want to use this to make an offer it would do that using + the make offer method. For each market joined either an offer callback or an aggregate callback is required. + You can’t supply both for any single market. + + :param price_callback: This callback is called when the market clears. The price callback is optional. + + :param error_callback: This callback is called at appropriate time points or when an error occurs. + If a market fails to form this will be called at the offer time. + If the market doesn’t receive all its offers this will be called at market clear time. + If the market fails to clear this would be called at the next reservation time. + This allows agents to respond at or near the normal time points. The error callback is optional. + """ + self.registrations.make_registration(market_name, buyer_seller, + reservation_callback, offer_callback, + aggregate_callback, price_callback, error_callback) + + def make_offer(self, market_name, buyer_seller, curve): + """ + This call makes an offer with the MarketService. + + :param market_name: The name of the market commodity. + + :param buyer_seller: A string indicating whether the agent is buying from or selling to the market. + The agent shall use the pre-defined strings provided. + + :param curve: The demand curve for buyers or the supply curve for sellers. + """ + result = self.registrations.make_offer(market_name, buyer_seller, curve) + return result + diff --git a/services/contrib/MarketServiceAgent/base_market_agent/buy_sell.py b/services/contrib/MarketServiceAgent/base_market_agent/buy_sell.py new file mode 100644 index 0000000000..606d5e3a32 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/buy_sell.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +BUYER = 'buyer' +SELLER = 'seller' diff --git a/services/contrib/MarketServiceAgent/base_market_agent/error_codes.py b/services/contrib/MarketServiceAgent/base_market_agent/error_codes.py new file mode 100644 index 0000000000..2ac5bbd067 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/error_codes.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +NOT_FORMED = 'not formed' +SHORT_OFFERS = 'not enough offers' +NO_INTERSECT = 'no curve intersection' +BAD_STATE = 'bad state transition' # This error should never happen. diff --git a/services/contrib/MarketServiceAgent/base_market_agent/market_registration.py b/services/contrib/MarketServiceAgent/base_market_agent/market_registration.py new file mode 100644 index 0000000000..def2e46872 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/market_registration.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging + +from volttron.platform.agent import utils +from volttron.platform.agent.base_market_agent.error_codes import NOT_FORMED + +_log = logging.getLogger(__name__) +utils.setup_logging() + +class MarketRegistration(object): + def __init__(self, market_name, buyer_seller, reservation_callback, offer_callback, + aggregate_callback, price_callback, error_callback, verbose_logging = True): + self.market_name = market_name + self.buyer_seller = buyer_seller + self.reservation_callback = reservation_callback + self.offer_callback = offer_callback + self.aggregate_callback = aggregate_callback + self.price_callback = price_callback + self.error_callback = error_callback + self.always_wants_reservation = self.reservation_callback == None + self.has_reservation = False + self.failed_to_form_error = False + self.verbose_logging = verbose_logging + self._validate_callbacks() + + def request_reservations(self, timestamp, rpc_proxy): + self.has_reservation = False + self.failed_to_form_error = False + if self.reservation_callback is not None: + wants_reservation_this_time = self.reservation_callback(timestamp, self.market_name, self.buyer_seller) + else: + wants_reservation_this_time = self.always_wants_reservation + if wants_reservation_this_time: + has_reservation = rpc_proxy.make_reservation(self.market_name, self.buyer_seller) + if has_reservation: + self.has_reservation = has_reservation + if self.verbose_logging: + _log.debug("Market: {} {} has obtained a reservation.".format(self.market_name, self.buyer_seller)) + else: + if self.verbose_logging: + _log.debug("Market: {} {} has failed to obtained a reservation.".format(self.market_name, self.buyer_seller)) + + def make_offer(self, buyer_seller, curve, rpc_proxy): + result = False + is_ok, error_message = self._ok_to_make_offer() + if is_ok: + result, error_message = rpc_proxy.make_offer(self.market_name, buyer_seller, curve) + if result and error_message is None: + error_message = "Market: {} {} offer was made and accepted.".format(self.market_name, self.buyer_seller) + _log.debug(error_message) + + return result, error_message + + def request_offers(self, timestamp): + is_ok, error_message = self._ok_to_make_offer_via_callback() + if is_ok: + self.offer_callback(timestamp, self.market_name, self.buyer_seller) + _log.debug("Market: {} {} offer callback was called.".format(self.market_name, self.buyer_seller)) + else: + _log.debug(error_message) + + def report_clear_price(self, timestamp, price, quantity): + if self.has_reservation and self.price_callback is not None: + self.price_callback(timestamp, self.market_name, self.buyer_seller, price, quantity) + if self.verbose_logging: + _log.debug("Market: {} {} Price: {} Quantity: {}".format(self.market_name, self.buyer_seller, price, quantity)) + self.has_reservation = False + + def report_aggregate(self, timestamp, buyer_seller, aggregate_curve): + if self.has_reservation and self.aggregate_callback is not None: + self.aggregate_callback(timestamp, self.market_name, buyer_seller, aggregate_curve) + if self.verbose_logging: + _log.debug("Market: {} {} Curve: {}".format(self.market_name, self.buyer_seller, aggregate_curve.points)) + + def report_error(self, timestamp, error_code, error_message, aux): + if error_code == NOT_FORMED: + self.failed_to_form_error = True + if self.error_callback is not None: + self.error_callback(timestamp, self.market_name, self.buyer_seller, error_code, error_message, aux) + if self.verbose_logging: + _log.debug("Market: {} {} Error: {} {}".format(self.market_name, self.buyer_seller, error_code, error_message)) + + def _validate_callbacks(self): + if self.offer_callback is None and self.aggregate_callback is None and self.price_callback is None: + raise TypeError("You must provide either an offer, aggregate, or price callback.") + + def _ok_to_make_offer_via_callback(self): + is_ok, error_message = self._ok_to_make_offer() + if self.offer_callback is None: + is_ok = False + error_message = "Market: {} {} offer failed because the agent has no offer callback.".format(self.market_name, self.buyer_seller) + return is_ok, error_message + + def _ok_to_make_offer(self): + is_ok = True + error_message = '' + if not self.has_reservation: + is_ok = False + error_message = "Market: {} {} offer failed because the market has no reservation.".format(self.market_name, self.buyer_seller) + if self.failed_to_form_error: + is_ok = False + error_message = "Market: {} {} offer failed because the market has not formed.".format(self.market_name, self.buyer_seller) + return is_ok, error_message diff --git a/services/contrib/MarketServiceAgent/base_market_agent/offer.py b/services/contrib/MarketServiceAgent/base_market_agent/offer.py new file mode 100644 index 0000000000..f3acc2b571 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/offer.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +class Offer(object): + + BUY = 'BUY' + SELL = 'SELL' + + def __init__(self, offer_type, commodity, curve): + self.__type = offer_type + self.__commodity = commodity + self.__curve = curve + + def type(self): + return self.__type + + def commodity(self): + return self.__commodity + + def curve(self): + return self.__curve diff --git a/services/contrib/MarketServiceAgent/base_market_agent/point.py b/services/contrib/MarketServiceAgent/base_market_agent/point.py new file mode 100644 index 0000000000..b9fa08d400 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/point.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from builtins import property as _property, tuple as _tuple +from operator import itemgetter as _itemgetter +from collections import OrderedDict + +class Point(tuple): + 'Point(quantity, price)' + __slots__ = () + + _fields = ('quantity', 'price') + + def __new__(_cls, quantity, price): + """Create new instance of Point(quantity, price)""" +# if (quantity < 0 or quantity is None): +# raise ValueError('The quantity provided ({}) is an invalid value.'.format(quantity)) +# if (price < 0 or price is None): +# raise ValueError('The price provided ({}) is an invalid value.'.format(price)) + # Catch exception to + float_quantity = float(quantity) + float_price = float(price) + return _tuple.__new__(_cls, (float_quantity, float_price)) + + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + """Make a new Point object from a sequence or iterable""" + result = new(cls, iterable) + if len(result) != 2: + raise TypeError('Expected 2 arguments, got %d' % len(result)) + return result + + def __repr__(self): + """Return a nicely formatted representation string""" + return 'Point(quantity=%r, price=%r)' % self + + def _asdict(self): + """Return a new OrderedDict which maps field names to their values""" + return OrderedDict(zip(self._fields, self)) + + def _replace(_self, **kwds): + """Return a new Point object replacing specified fields with new values""" + result = _self._make(map(kwds.pop, ('quantity', 'price'), _self)) + if kwds: + raise ValueError('Got unexpected field names: %r' % kwds.keys()) + return result + + def __getnewargs__(self): + """Return self as a plain tuple. Used by copy and pickle.""" + return tuple(self) + + __dict__ = _property(_asdict) + + def __getstate__(self): + """Exclude the OrderedDict from pickling""" + pass + + def tuppleize(self): + return (self.quantity, self.price) + + quantity = _property(_itemgetter(0), doc='Alias for field number 0') + x = _property(_itemgetter(0), doc='Alias for field number 0') + + price = _property(_itemgetter(1), doc='Alias for field number 1') + y = _property(_itemgetter(1), doc='Alias for field number 1') diff --git a/services/contrib/MarketServiceAgent/base_market_agent/poly_line.py b/services/contrib/MarketServiceAgent/base_market_agent/poly_line.py new file mode 100644 index 0000000000..943e180587 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/poly_line.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import numpy as np + +def cmp(a, b): + return (a > b) - (a < b) + +class PolyLine: + def __init__(self): + self.points = [] + self.xs = None + self.ys = None + self.xsSortedByY = None + self.ysSortedByY = None + self._min_x = None + self._max_x = None + self._min_y = None + self._max_y = None + + def add(self, point): + if self.points is None: + self.points = [] + if len(self.points) > 0: + for p in reversed(self.points): + if p.x == point.x and p.y == point.y: + return + doSort = False + # if len(self.points) > 0 and point.y < self.points[-1].y: + if len(self.points) > 0: + doSort = True + + self.points.append(point) + if doSort: + self.points.sort(key=lambda tup: tup[1], reverse=True) + self.xs = None + self.ys = None + if point.x is not None and point.y is not None: + self._min_x = PolyLine.min(self._min_x, point.x) + self._min_y = PolyLine.min(self._min_y, point.y) + self._max_x = PolyLine.max(self._max_x, point.x) + self._max_y = PolyLine.max(self._max_y, point.y) + + def contains_none(self): + result = False + if self.points is not None and len(self.points) > 0: + for p in self.points: + if p.x is None or p.y is None: + result = True + return result + + @staticmethod + def min(x1, x2): + if x1 is None: + return x2 + if x2 is None: + return x1 + return min(x1, x2) + + @staticmethod + def max(x1, x2): + if x1 is None: + return x2 + if x2 is None: + return x1 + return max(x1, x2) + + @staticmethod + def sum(x1, x2): + if x1 is None: + return x2 + if x2 is None: + return x1 + return x1 + x2 + + def x(self, y): + if not self.points: + return None + if y is None: + return None + self.vectorize() + # return np.interp(y, self.ys, self.xs) #, right=0.) .. we learned that this gave weird results previously + # ascending = self.ys[0] (p2[1] - p1[1]) * (p3[0] - p1[0]) + + @staticmethod + def segment_intersects(l1, l2): + if l1[0][0] is None or l1[0][1] is None or l1[1][0] is None or l1[1][1] is None: + return False + if l2[0][0] is None or l2[0][1] is None or l2[1][0] is None or l2[1][1] is None: + return False + if (PolyLine.ccw(l1[0], l2[0], l2[1]) != PolyLine.ccw(l1[1], l2[0], l2[1]) + and PolyLine.ccw(l1[0], l1[1], l2[0]) != PolyLine.ccw(l1[0], l1[1], l2[1])): + return True + if (l1[0][0] == l2[0][0] and l1[0][1] == l2[0][1]) or (l1[0][0] == l2[1][0] and l1[0][1] == l2[1][1]): + return True + if (l1[1][0] == l2[0][0] and l1[1][1] == l2[0][1]) or (l1[1][0] == l2[1][0] and l1[1][1] == l2[1][1]): + return True + + @staticmethod + def between(a, b, c): + if (a[0] is None or a[1] is None or b[0] is None or b[1] is None or c[0] is None or c[1] is None): + return None + crossproduct = (c[1] - a[1]) * (b[0] - a[0]) - (c[0] - a[0]) * (b[1] - a[1]) + if abs(crossproduct) > 1e-12: + return False + dotproduct = (c[0] - a[0]) * (b[0] - a[0]) + (c[1] - a[1]) * (b[1] - a[1]) + if dotproduct < 0: + return False + squaredlengthba = (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) + if dotproduct > squaredlengthba: + return False + return True + + @staticmethod + def intersection(pl_1, pl_2): + pl_1 = pl_1.points + pl_2 = pl_2.points + + # we have two points + if len(pl_1) == 1 and len(pl_2) == 1: + if pl_1[0][0] == pl_2[0][0] and pl_1[0][1] == pl_2[0][1]: + quantity = pl_1[0][0] + price = pl_1[0][1] + return quantity, price + + # we have one point and line segments + elif len(pl_1) == 1 or len(pl_2) == 1: + if len(pl_1) == 1: + point = pl_1[0] + line = pl_2 + else: + point = pl_2[0] + line = pl_1 + for j, pl_2_1 in enumerate(line[:-1]): + pl_2_2 = line[j + 1] + if PolyLine.between(pl_2_1, pl_2_2, point): + quantity = point[0] + price = point[1] + return quantity, price + + # we have line segments + elif len(pl_1) > 1 and len(pl_2) > 1: + for i, pl_1_1 in enumerate(pl_1[:-1]): + pl_1_2 = pl_1[i + 1] + for j, pl_2_1 in enumerate(pl_2[:-1]): + pl_2_2 = pl_2[j + 1] + if PolyLine.segment_intersects((pl_1_1, pl_1_2), (pl_2_1, pl_2_2)): + quantity, price = PolyLine.segment_intersection((pl_1_1, pl_1_2), (pl_2_1, pl_2_2)) + return quantity, price + p1_qmax = max([point[0] for point in pl_1]) + p1_qmin = min([point[0] for point in pl_1]) + + p2_qmax = max([point[0] for point in pl_2]) + p2_qmin = min([point[0] for point in pl_2]) + + p1_pmax = max([point[1] for point in pl_1]) + p2_pmax = max([point[1] for point in pl_2]) + + p1_pmin = min([point[1] for point in pl_1]) + p2_pmin = min([point[1] for point in pl_2]) + # The lines don't intersect, add the auxillary information + # TODO - clean this method up. + if p1_pmax <= p2_pmax and p1_pmax <=p2_pmin: + quantity = p1_qmin + price = p2_pmax + + elif p2_pmin <=p1_pmin and p2_pmax <=p1_pmin: + quantity = p1_qmax + price = p2_pmin + + elif p2_qmax >= p1_qmin and p2_qmax >= p1_qmax: + quantity = np.mean([point[0] for point in pl_1]) + price = np.mean([point[1] for point in pl_1]) + + elif p2_qmin <= p1_qmin and p2_qmin <= p1_qmax: + quantity = p2_qmax + price = p1_pmax + + else: + price = None + quantity = None + + return quantity, price + + @staticmethod + def line_intersection(line1, line2): + x1x3 = line1[0][0]-line2[0][0] + y3y4 = line2[0][1]-line2[1][1] + y1y3 = line1[0][1]-line2[0][1] + y1y2 = line1[0][1]-line1[1][1] + x3x4 = line2[0][0]-line2[1][0] + x1x2 = line1[0][0]-line1[1][0] + y1y3 = line1[0][1]-line2[0][1] + if x1x2*y3y4 - y1y2*x3x4 == 0: + return None + t = (x1x3*y3y4 - y1y3*x3x4)/(x1x2*y3y4 - y1y2*x3x4) + # u=(x1x2*y1y3-y1y2*x1x3)/(x1x2*y3y4-y1y2*x3x4) + x = line1[0][0] + t*(line1[1][0] - line1[0][0]) + y = line1[0][1] + t*(line1[1][1] - line1[0][1]) + # if x>max(line1[0][0],line1[1][0]) or x>max(line2[0][0],line2[1][0]) or xmax(line1[0][1],line1[1][1]) or y>max(line2[0][1],line2[1][1]) or y max(line1[0][1], line1[1][1]): + return min(line1[0][0], line1[1][0]), y + if y > max(line2[0][1], line2[1][1]): + return min(line2[0][0], line2[1][0]), y + if y < min(line1[0][1], line1[1][1]): + return max(line1[0][0], line1[1][0]), y + if y < min(line2[0][1], line2[1][1]): + return max(line2[0][0], line2[1][0]), y + return x, y + + @staticmethod + def poly_intersection(poly1, poly2): + poly1 = poly1.points + poly2 = poly2.points + for i, p1_first_point in enumerate(poly1[:-1]): + p1_second_point = poly1[i + 1] + + for j, p2_first_point in enumerate(poly2[:-1]): + p2_second_point = poly2[j + 1] + + if PolyLine.line_intersection((p1_first_point, p1_second_point), (p2_first_point, p2_second_point)): + x, y = PolyLine.line_intersection((p1_first_point, p1_second_point), (p2_first_point, p2_second_point)) + return x, y + + return False + + @staticmethod + def compare(demand_curve, supply_curve): + aux = {} + demand_max_quantity = demand_curve.max_x() + demand_min_quantity = demand_curve.min_x() + supply_max_quantity = supply_curve.max_x() + supply_min_quantity = supply_curve.min_x() + demand_max_price = demand_curve.max_y() + demand_min_price = demand_curve.min_y() + supply_max_price = supply_curve.max_y() + supply_min_price = supply_curve.min_y() + + aux['SQn,DQn'] = (supply_min_quantity > demand_min_quantity) - (supply_min_quantity < demand_min_quantity) + aux['SQn,DQx'] = (supply_min_quantity > demand_max_quantity) - (supply_min_quantity < demand_max_quantity) + aux['SQx,DQn'] = (supply_max_quantity > demand_min_quantity) - (supply_max_quantity < demand_min_quantity) + aux['SQx,DQx'] = (supply_max_quantity > demand_max_quantity) - (supply_max_quantity < demand_max_quantity) + + aux['SPn,DPn'] = (supply_min_price > demand_min_price) - (supply_min_price < demand_min_price) + aux['SPn,DPx'] = (supply_min_price > demand_max_price) - (supply_min_price < demand_max_price) + aux['SPx,DPn'] = (supply_max_price > demand_min_price) - (supply_max_price < demand_min_price) + aux['SPx,DPx'] = (supply_max_price > demand_max_price) - (supply_max_price < demand_max_price) + return aux diff --git a/services/contrib/MarketServiceAgent/base_market_agent/poly_line_factory.py b/services/contrib/MarketServiceAgent/base_market_agent/poly_line_factory.py new file mode 100644 index 0000000000..56a5606275 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/poly_line_factory.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import numpy as np +import logging +from volttron.platform.agent.base_market_agent.point import Point +from volttron.platform.agent.base_market_agent.poly_line import PolyLine +from volttron.platform.agent import utils + +_log = logging.getLogger(__name__) +utils.setup_logging() + + +def remove(duplicate): + final_list = [] + for num in duplicate: + if num not in final_list: + final_list.append(num) + return final_list + + +class PolyLineFactory(object): + @staticmethod + def combine(lines, increment): + + # we return a new PolyLine which is a composite (summed horizontally) of inputs + composite = PolyLine() + + # find the range defined by the curves + minY = None + maxY = None + for l in lines: + minY = PolyLine.min(minY, l.min_y()) + maxY = PolyLine.max(maxY, l.max_y()) + + # special case if the lines are already horizontal or None + if minY == maxY: + minSumX = None + maxSumX = None + for line in lines: + minX = None + maxX = None + for point in line.points: + minX = PolyLine.min(minX, point.x) + maxX = PolyLine.max(maxX, point.x) + minSumX = PolyLine.sum(minSumX, minX) + maxSumX = PolyLine.sum(maxSumX, maxX) + composite.add(Point(minSumX, minY)) + if minX != maxX: + composite.add(Point(maxSumX, maxY)) + return composite + + # create an array of ys in equal increments, with highest first + # this is assuming that price decreases with increase in demand (buyers!) + # but seems to work with multiple suppliers? + ys = sorted(np.linspace(minY, maxY, num=increment), reverse=True) + + # now find the cumulative x associated with each y in the array + # starting with the highest y + for y in ys: + xt = None + for line in lines: + x = line.x(y) + # print x, y + if x is not None: + xt = x if xt is None else xt + x + composite.add(Point(xt, y)) + + return composite + + @staticmethod + def combine_withoutincrement(lines): + + # we return a new PolyLine which is a composite (summed horizontally) of inputs + composite = PolyLine() + if len(lines) < 2: + if isinstance(lines[0], list): + for point in lines[0]: + composite.add(Point(point[0], point[1])) + return composite + return lines[0] + # find the range defined by the curves + ys=[] + for l in lines: + ys=ys+l.vectorize()[1] + + ys = remove(ys) + + ys.sort(reverse=True) + for y in ys: + xt = None + for line in lines: + x = line.x(y) + if x is not None: + xt = x if xt is None else xt + x + composite.add(Point(xt, y)) + return composite + + @staticmethod + def fromTupples(points): + poly_line = PolyLine() + for p in points: + if p is not None and len(p) == 2: + poly_line.add(Point(p[0], p[1])) + return poly_line + diff --git a/services/contrib/MarketServiceAgent/base_market_agent/registration_manager.py b/services/contrib/MarketServiceAgent/base_market_agent/registration_manager.py new file mode 100644 index 0000000000..f0d9b68af5 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/registration_manager.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging +import gevent + +from volttron.platform.agent import utils +from volttron.platform.agent.base_market_agent.error_codes import NOT_FORMED +from volttron.platform.agent.base_market_agent.market_registration import MarketRegistration + +_log = logging.getLogger(__name__) +utils.setup_logging() + +GREENLET_ENABLED = False + + +class RegistrationManager(object): + """ + The ReservationManager manages a list of MarketReservations for the MarketAgents. + This class exists to hide the features of the underlying collection that are not relevant to + managing market reservations. + """ + def __init__(self, rpc_proxy): + """ + The initalization needs the agent to grant access to the RPC calls needed to + communicate with the marketService. + :param rpc_proxy: The MarketAgents that owns this object. + """ + self.registrations = [] + self.rpc_proxy = rpc_proxy + + def make_registration(self, market_name, buyer_seller, reservation_callback, offer_callback, + aggregate_callback, price_callback, error_callback): + registration = MarketRegistration(market_name, buyer_seller, reservation_callback, offer_callback, + aggregate_callback, price_callback, error_callback) + self.registrations.append(registration) + + def make_offer(self, market_name, buyer_seller, curve): + result = False + error_message = "Market: {} {} was not found in the local list of markets".format(market_name, buyer_seller) + for registration in self.registrations: + if registration.market_name == market_name: + result, error_message = registration.make_offer(buyer_seller, curve, self.rpc_proxy) + return result, error_message + + def request_reservations(self, timestamp): + greenlets = [] + _log.debug("Registration manager request_reservations") + for registration in self.registrations: + if GREENLET_ENABLED: + event = gevent.spawn(registration.request_reservations, timestamp, self.rpc_proxy) + greenlets.append(event) + else: + registration.request_reservations(timestamp, self.rpc_proxy) + gevent.joinall(greenlets) + _log.debug("After request reserverations!") + + def request_offers(self, timestamp, unformed_markets): + greenlets = [] + _log.debug("Registration manager request_offers") + for registration in self.registrations: + if registration.market_name not in unformed_markets: + if GREENLET_ENABLED: + event = gevent.spawn(registration.request_offers, timestamp) + greenlets.append(event) + else: + registration.request_offers(timestamp) + else: + error_message = 'The market {} has not received a buy and a sell reservation.'.format(registration.market_name) + registration.report_error(timestamp, NOT_FORMED, error_message, {}) + gevent.joinall(greenlets) + _log.debug("After request offers!") + + def report_clear_price(self, timestamp, market_name, price, quantity): + for registration in self.registrations: + if registration.market_name == market_name: + registration.report_clear_price(timestamp, price, quantity) + + def report_aggregate(self, timestamp, market_name, buyer_seller, aggregate_curve): + for registration in self.registrations: + if registration.market_name == market_name: + registration.report_aggregate(timestamp, buyer_seller, aggregate_curve) + + def report_error(self, timestamp, market_name, error_code, error_message, aux): + for registration in self.registrations: + if registration.market_name == market_name: + registration.report_error(timestamp, error_code, error_message, aux) diff --git a/services/contrib/MarketServiceAgent/base_market_agent/rpc_proxy.py b/services/contrib/MarketServiceAgent/base_market_agent/rpc_proxy.py new file mode 100644 index 0000000000..f544e394e5 --- /dev/null +++ b/services/contrib/MarketServiceAgent/base_market_agent/rpc_proxy.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging +import gevent + +from volttron.platform.agent import utils +from volttron.platform.agent.known_identities import PLATFORM_MARKET_SERVICE +from volttron.platform.jsonrpc import RemoteError + +_log = logging.getLogger(__name__) +utils.setup_logging() + + +class RpcProxy(object): + """ + The purpose of the RpcProxy is to allow the MarketRegistration to make + RPC calls on the agent that subclasses of the agent can't see and therefore + can't make. + """ + def __init__(self, rpc_call, verbose_logging=True, timeout=60): + """ + The initalization needs the rpc_call method to grant access to the RPC calls needed to + communicate with the marketService. + :param rpc_call: The MarketAgent owns this object. + """ + self.rpc_call = rpc_call + self.verbose_logging = verbose_logging + self.timeout = timeout + + def make_reservation(self, market_name, buyer_seller): + """ + This call makes a reservation with the MarketService. This allows the agent to submit a bid and receive + a cleared market price. + + :param market_name: The name of the market commodity. + + :param buyer_seller: A string indicating whether the agent is buying from or selling to the market. + The agent shall use the pre-defined strings provided. + """ + try: + self.rpc_call(PLATFORM_MARKET_SERVICE, 'make_reservation', market_name, buyer_seller).get(timeout=self.timeout) + has_reservation = True + except RemoteError as e: + has_reservation = False + except gevent.Timeout as e: + has_reservation = False + return has_reservation + + def make_offer(self, market_name, buyer_seller, curve): + """ + This call makes an offer with the MarketService. + + :param market_name: The name of the market commodity. + + :param buyer_seller: A string indicating whether the agent is buying from or selling to the market. + The agent shall use the pre-defined strings provided. + + :param curve: The demand curve for buyers or the supply curve for sellers. + """ + try: + self.rpc_call(PLATFORM_MARKET_SERVICE, 'make_offer', market_name, buyer_seller, + curve.tuppleize()).get(timeout=self.timeout) + result = (True, None) + if self.verbose_logging: + _log.debug("Market: {} {} has made an offer Curve: {}".format(market_name, + buyer_seller, + curve.points)) + except RemoteError as e: + result = (False, e.message) + _log.info( + "Market: {} {} has had an offer rejected because {}".format(market_name, buyer_seller, e.message)) + except gevent.Timeout as e: + result = (False, e.message) + _log.info("Market: {} {} has had an offer rejected because {}".format(market_name, buyer_seller, e.message)) + return result + + diff --git a/services/contrib/MarketServiceAgent/config b/services/contrib/MarketServiceAgent/config new file mode 100644 index 0000000000..b2686b623c --- /dev/null +++ b/services/contrib/MarketServiceAgent/config @@ -0,0 +1,6 @@ +{ + "market_period" : 300, + "reservation_delay": 0, + "offer_delay" : 120, + "verbose_logging" : 0 +} diff --git a/services/contrib/MarketServiceAgent/conftest.py b/services/contrib/MarketServiceAgent/conftest.py new file mode 100644 index 0000000000..68e5e611b1 --- /dev/null +++ b/services/contrib/MarketServiceAgent/conftest.py @@ -0,0 +1,6 @@ +import sys + +from volttrontesting.fixtures.volttron_platform_fixtures import * + +# Add system path of the agent's directory +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) diff --git a/services/contrib/MarketServiceAgent/market_service/__init__.py b/services/contrib/MarketServiceAgent/market_service/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/contrib/MarketServiceAgent/market_service/agent.py b/services/contrib/MarketServiceAgent/market_service/agent.py new file mode 100644 index 0000000000..5803a07cc3 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/agent.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +""" +.. _market-service-agent: + +The Market Service Agent is used to allow agents to use transactive markets +to implement transactive control strategies. The Market Service Agent provides +an implementation of double blind auction markets that can be used by multiple agents. + +Agents that want to use the Market Service Agent inherit from the :ref:`base MarketAgent`. +The base MarketAgent handles all of the communication between the agent and the MarketServiceAgent. + +MarketServiceAgent Configuration +================================ + + "market_period" + The time allowed for a market cycle in seconds. After this amount of time the market starts again. + Defaults to 300. + "reservation_delay" + The time delay between the start of a market cycle and the start of gathering market reservations + in seconds. Defaults to 0. + "offer_delay" + The time delay between the start of gathering market reservations and the start of gathering market bids/offers + in seconds. Defaults to 120. + "verbose_logging" + If True this enables verbose logging. If False, there is little or no logging. + Defaults to True. + + +Sample configuration file +------------------------- + +.. code-block:: python + + { + "market_period": 300, + "reservation_delay": 0, + "offer_delay": 120, + "verbose_logging": True + } + +""" + +__docformat__ = 'reStructuredText' + +import logging +import sys +import gevent + +from transitions import Machine +from volttron.platform.agent.known_identities import PLATFORM_MARKET_SERVICE +from volttron.platform.agent import utils +from volttron.platform.messaging.topics import MARKET_RESERVE, MARKET_BID +from volttron.platform.vip.agent import Agent, Core, RPC +from volttron.platform.agent.base_market_agent.poly_line_factory import PolyLineFactory + +from .market_list import MarketList +from .market_participant import MarketParticipant +from .director import Director + +_tlog = logging.getLogger('transitions.core') +_tlog.setLevel(logging.WARNING) +_log = logging.getLogger(__name__) +utils.setup_logging() +__version__ = "1.0" + +INITIAL_WAIT = 'service_initial_wait' +COLLECT_RESERVATIONS = 'service_collect_reservations' +COLLECT_OFFERS = 'service_collect_offers' +NO_MARKETS = 'service_has_no_markets' + + +class MarketServiceAgent(Agent): + states = [INITIAL_WAIT, COLLECT_RESERVATIONS, COLLECT_OFFERS, NO_MARKETS] + transitions = [ + {'trigger': 'start_reservations', 'source': INITIAL_WAIT, 'dest': COLLECT_RESERVATIONS}, + {'trigger': 'start_offers_no_markets', 'source': COLLECT_RESERVATIONS, 'dest': NO_MARKETS}, + {'trigger': 'start_offers_has_markets', 'source': COLLECT_RESERVATIONS, 'dest': COLLECT_OFFERS}, + {'trigger': 'start_reservations', 'source': COLLECT_OFFERS, 'dest': COLLECT_RESERVATIONS}, + {'trigger': 'start_reservations', 'source': NO_MARKETS, 'dest': COLLECT_RESERVATIONS}, + ] + + def __init__(self, config_path, **kwargs): + super(MarketServiceAgent, self).__init__(**kwargs) + + config = utils.load_config(config_path) + self.agent_name = config.get('agent_name', 'MixMarketService') + self.market_period = int(config.get('market_period', 300)) + self.reservation_delay = int(config.get('reservation_delay', 0)) + self.offer_delay = int(config.get('offer_delay', 120)) + self.verbose_logging = int(config.get('verbose_logging', True)) + self.director = None + # This can be periodic or event_driven + self.market_type = config.get("market_type", "event_driven") + if self.market_type not in ["periodic", "event_driven"]: + self.market_type = "event_driven" + + self.state_machine = Machine(model=self, states=MarketServiceAgent.states, + transitions= MarketServiceAgent.transitions, initial=INITIAL_WAIT) + self.market_list = MarketList(self.vip.pubsub.publish, self.verbose_logging) + + @Core.receiver("onstart") + def onstart(self, sender, **kwargs): + if self.market_type == "periodic": + self.director = Director(self.market_period, self.reservation_delay, self.offer_delay) + self.director.start(self) + else: + # Listen to the new_cycle signal + self.vip.pubsub.subscribe(peer='pubsub', + prefix='mixmarket/start_new_cycle', + callback=self.start_new_cycle) + + def start_new_cycle(self, peer, sender, bus, topic, headers, message): + _log.debug("Trigger market period for Market agent.") + gevent.sleep(self.reservation_delay) + self.send_collect_reservations_request(utils.get_aware_utc_now()) + + gevent.sleep(self.offer_delay) + self.send_collect_offers_request(utils.get_aware_utc_now()) + + def send_collect_reservations_request(self, timestamp): + _log.debug("send_collect_reservations_request at {}".format(timestamp)) + self.start_reservations() + self.market_list.send_market_failure_errors() + self.market_list.clear_reservations() + self.vip.pubsub.publish(peer='pubsub', + topic=MARKET_RESERVE, + message=utils.format_timestamp(timestamp)) + + def send_collect_offers_request(self, timestamp): + if self.has_any_markets(): + self.begin_collect_offers(timestamp) + else: + self.start_offers_no_markets() + + def begin_collect_offers(self, timestamp): + _log.debug("send_collect_offers_request at {}".format(timestamp)) + self.start_offers_has_markets() + self.market_list.collect_offers() + unformed_markets = self.market_list.unformed_market_list() + self.vip.pubsub.publish(peer='pubsub', + topic=MARKET_BID, + message=[utils.format_timestamp(timestamp), unformed_markets]) + + @RPC.export + def make_reservation(self, market_name, buyer_seller): + import time + start = time.time() + + identity = bytes(self.vip.rpc.context.vip_message.peer, "utf8") + log_message = "Received {} reservation for market {} from agent {}".format(buyer_seller, market_name, identity) + _log.debug(log_message) + if self.state == COLLECT_RESERVATIONS: + self.accept_reservation(buyer_seller, identity, market_name) + else: + self.reject_reservation(buyer_seller, identity, market_name) + + end = time.time() + print(end - start) + + def accept_reservation(self, buyer_seller, identity, market_name): + _log.info("Reservation on Market: {} {} made by {} was accepted.".format(market_name, buyer_seller, identity)) + participant = MarketParticipant(buyer_seller, identity) + self.market_list.make_reservation(market_name, participant) + + def reject_reservation(self, buyer_seller, identity, market_name): + _log.info("Reservation on Market: {} {} made by {} was rejected.".format(market_name, buyer_seller, identity)) + raise RuntimeError("Error: Market service not accepting reservations at this time.") + + @RPC.export + def make_offer(self, market_name, buyer_seller, offer): + identity = bytes(self.vip.rpc.context.vip_message.peer, "utf8") + log_message = "Received {} offer for market {} from agent {}".format(buyer_seller, market_name, identity) + _log.debug(log_message) + if self.state == COLLECT_OFFERS: + self.accept_offer(buyer_seller, identity, market_name, offer) + else: + self.reject_offer(buyer_seller, identity, market_name, offer) + + def accept_offer(self, buyer_seller, identity, market_name, offer): + _log.info("Offer on Market: {} {} made by {} was accepted.".format(market_name, buyer_seller, identity)) + participant = MarketParticipant(buyer_seller, identity) + curve = PolyLineFactory.fromTupples(offer) + self.market_list.make_offer(market_name, participant, curve) + + def reject_offer(self, buyer_seller, identity, market_name, offer): + _log.info("Offer on Market: {} {} made by {} was rejected.".format(market_name, buyer_seller, identity)) + raise RuntimeError("Error: Market service not accepting offers at this time.") + + def has_any_markets(self): + unformed_markets = self.market_list.unformed_market_list() + return len(unformed_markets) < self.market_list.market_count() + + +def main(): + """Main method called to start the agent.""" + utils.vip_main(MarketServiceAgent, + identity=PLATFORM_MARKET_SERVICE, + version=__version__) + + +if __name__ == '__main__': + # Entry point for script + try: + sys.exit(main()) + except KeyboardInterrupt: + pass diff --git a/services/contrib/MarketServiceAgent/market_service/director.py b/services/contrib/MarketServiceAgent/market_service/director.py new file mode 100644 index 0000000000..55679d6890 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/director.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging +import gevent +from volttron.platform.agent import utils +from volttron.platform.scheduling import periodic + +_log = logging.getLogger(__name__) +utils.setup_logging() + +class Director(object): + def __init__(self, market_period, reservation_delay, offer_delay): + _log.debug("Creating Director for MarketServiceAgent") + self.market_period = market_period + self.reservation_delay = reservation_delay + self.offer_delay = offer_delay + + def start(self, sender): + _log.debug("Starting Director for MarketServiceAgent") + self.sender = sender + self.sender.core.schedule(periodic(self.market_period), self._trigger) + + def _trigger(self): + _log.debug("Trigger market period in Director for MarketServiceAgent") + gevent.sleep(self.reservation_delay) + self.sender.send_collect_reservations_request(self._get_time()) + gevent.sleep(self.offer_delay) + self.sender.send_collect_offers_request(self._get_time()) + + def _get_time(self): + now = utils.get_aware_utc_now() + return now diff --git a/services/contrib/MarketServiceAgent/market_service/market.py b/services/contrib/MarketServiceAgent/market_service/market.py new file mode 100644 index 0000000000..ed397bca80 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/market.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +ACCEPT_RESERVATIONS = 'market_accept_resevations' +ACCEPT_RESERVATIONS_HAS_FORMED = 'market_accept_resevations_market_has_formed' +ACCEPT_ALL_OFFERS = 'market_accept_all_offers' +ACCEPT_BUY_OFFERS = 'market_accept_buy_offers' +ACCEPT_SELL_OFFERS = 'market_accept_sell_offers' +MARKET_DONE = 'market_done' + +import logging +from transitions import Machine +from volttron.platform.agent import utils +from volttron.platform.agent.base_market_agent.error_codes import NOT_FORMED, SHORT_OFFERS, BAD_STATE, NO_INTERSECT +from volttron.platform.agent.base_market_agent.buy_sell import BUYER, SELLER +from volttron.platform.messaging.topics import MARKET_AGGREGATE, MARKET_CLEAR, MARKET_ERROR, MARKET_RECORD + +from .offer_manager import OfferManager +from .reservation_manager import ReservationManager + +_tlog = logging.getLogger('transitions.core') +_tlog.setLevel(logging.WARNING) +_log = logging.getLogger(__name__) +utils.setup_logging() + + +class MarketFailureError(Exception): + """Base class for exceptions in this module.""" + def __init__(self, market_name, market_state, object_type, participant): + name = role = '' + if participant is not None: + name = participant.identity + role = participant.buyer_seller + super(MarketFailureError, self).__init__('The market {} is not accepting {} ' + 'at this time. The state is {}. Participant info: {} {}.'.format(market_name, + object_type, market_state, name, role)) + + +class Market(object): + states = [ACCEPT_RESERVATIONS, ACCEPT_RESERVATIONS_HAS_FORMED, ACCEPT_ALL_OFFERS, ACCEPT_BUY_OFFERS, ACCEPT_SELL_OFFERS, MARKET_DONE] + transitions = [ + {'trigger': 'receive_reservation', 'source': ACCEPT_RESERVATIONS, 'dest': ACCEPT_RESERVATIONS}, + {'trigger': 'market_forms', 'source': ACCEPT_RESERVATIONS, 'dest': ACCEPT_RESERVATIONS_HAS_FORMED}, + {'trigger': 'start_offers', 'source': ACCEPT_RESERVATIONS, 'dest': MARKET_DONE}, + {'trigger': 'receive_buy_offer', 'source': ACCEPT_RESERVATIONS, 'dest': MARKET_DONE}, + {'trigger': 'receive_sell_offer', 'source': ACCEPT_RESERVATIONS, 'dest': MARKET_DONE}, + + {'trigger': 'receive_reservation', 'source': ACCEPT_RESERVATIONS_HAS_FORMED, 'dest': ACCEPT_RESERVATIONS_HAS_FORMED}, + {'trigger': 'start_offers', 'source': ACCEPT_RESERVATIONS_HAS_FORMED, 'dest': ACCEPT_ALL_OFFERS}, + {'trigger': 'receive_buy_offer', 'source': ACCEPT_RESERVATIONS_HAS_FORMED, 'dest': MARKET_DONE}, + {'trigger': 'receive_sell_offer', 'source': ACCEPT_RESERVATIONS_HAS_FORMED, 'dest': MARKET_DONE}, + + {'trigger': 'receive_reservation', 'source': ACCEPT_ALL_OFFERS, 'dest': ACCEPT_ALL_OFFERS}, + {'trigger': 'receive_sell_offer', 'source': ACCEPT_ALL_OFFERS, 'dest': ACCEPT_ALL_OFFERS}, + {'trigger': 'receive_buy_offer', 'source': ACCEPT_ALL_OFFERS, 'dest': ACCEPT_ALL_OFFERS}, + {'trigger': 'last_sell_offer', 'source': ACCEPT_ALL_OFFERS, 'dest': ACCEPT_BUY_OFFERS}, + {'trigger': 'last_buy_offer', 'source': ACCEPT_ALL_OFFERS, 'dest': ACCEPT_SELL_OFFERS}, + + {'trigger': 'receive_reservation', 'source': ACCEPT_BUY_OFFERS, 'dest': ACCEPT_BUY_OFFERS}, + {'trigger': 'receive_sell_offer', 'source': ACCEPT_BUY_OFFERS, 'dest': ACCEPT_BUY_OFFERS}, + {'trigger': 'receive_buy_offer', 'source': ACCEPT_BUY_OFFERS, 'dest': ACCEPT_BUY_OFFERS}, + {'trigger': 'last_buy_offer', 'source': ACCEPT_BUY_OFFERS, 'dest': MARKET_DONE}, + + {'trigger': 'receive_reservation', 'source': ACCEPT_SELL_OFFERS, 'dest': ACCEPT_SELL_OFFERS}, + {'trigger': 'receive_sell_offer', 'source': ACCEPT_SELL_OFFERS, 'dest': ACCEPT_SELL_OFFERS}, + {'trigger': 'receive_buy_offer', 'source': ACCEPT_SELL_OFFERS, 'dest': ACCEPT_SELL_OFFERS}, + {'trigger': 'last_sell_offer', 'source': ACCEPT_SELL_OFFERS, 'dest': MARKET_DONE}, + + {'trigger': 'receive_reservation', 'source': MARKET_DONE, 'dest': MARKET_DONE}, + {'trigger': 'receive_sell_offer', 'source': MARKET_DONE, 'dest': MARKET_DONE}, + {'trigger': 'receive_buy_offer', 'source': MARKET_DONE, 'dest': MARKET_DONE}, + ] + + def __init__(self, market_name, participant, publish, verbose_logging = True): + self.reservations = ReservationManager() + self.offers = OfferManager() + self.market_name = market_name + self.publish = publish + self.verbose_logging = verbose_logging + _log.debug("Initializing Market: {} {} verbose logging is {}.".format(self.market_name, + participant.buyer_seller, self.verbose_logging)) + self.state_machine = Machine(model=self, states=Market.states, + transitions= Market.transitions, initial=ACCEPT_RESERVATIONS) + self.make_reservation(participant) + + def make_reservation(self, participant): + if self.verbose_logging: + _log.debug("Make reservation Market: {} {} entered in state {}".format(self.market_name, + participant.buyer_seller, + self.state)) + self.receive_reservation() + market_already_formed = self.has_market_formed() + if self.state not in [ACCEPT_RESERVATIONS, ACCEPT_RESERVATIONS_HAS_FORMED]: + raise MarketFailureError(self.market_name, self.state, 'reservations', participant) + self.reservations.make_reservation(participant) + if self.verbose_logging: + if participant.buyer_seller == BUYER: + reservation_count = self.reservations.buyer_count() + else: + reservation_count = self.reservations.seller_count() + _log.debug("Make reservation Market: {} {} now has {} reservations.".format(self.market_name, + participant.buyer_seller, reservation_count)) + if not market_already_formed and self.has_market_formed(): + self.market_forms() + if self.verbose_logging: + _log.debug("Make reservation Market: {} {} exited in state {}".format(self.market_name, + participant.buyer_seller, + self.state)) + + def make_offer(self, participant, curve): + if self.verbose_logging: + _log.debug("Make offer Market: {} {} entered in state {}".format(self.market_name, + participant.buyer_seller, + self.state)) + if (participant.buyer_seller == SELLER): + self.receive_sell_offer() + else: + self.receive_buy_offer() + if self.state not in [ACCEPT_ALL_OFFERS, ACCEPT_BUY_OFFERS, ACCEPT_SELL_OFFERS]: + raise MarketFailureError(self.market_name, self.state, 'offers', participant) + self.reservations.take_reservation(participant) + if self.verbose_logging: + if participant.buyer_seller == BUYER: + offer_count = self.offers.buyer_count() + else: + offer_count = self.reservations.seller_count() + _log.debug("Make offer Market: {} {} now has {} offers. Curve: {}".format(self.market_name, + participant.buyer_seller, offer_count, curve.tuppleize())) + self.offers.make_offer(participant.buyer_seller, curve) + if self.all_satisfied(participant.buyer_seller): + if (participant.buyer_seller == SELLER): + self.last_sell_offer() + else: + self.last_buy_offer() + + # Aggregate curve + aggregate_curve = self.offers.aggregate_curves(participant.buyer_seller) + if self.verbose_logging: + _log.debug("Report aggregate Market: {} {} Curve: {}".format(self.market_name, + participant.buyer_seller, aggregate_curve.tuppleize())) + + # Publish message with clearing price & aggregate curve + if aggregate_curve is not None: + timestamp = self._get_time() + timestamp_string = utils.format_timestamp(timestamp) + self.publish(peer='pubsub', + topic=MARKET_AGGREGATE, + message=[timestamp_string, self.market_name, + participant.buyer_seller, aggregate_curve.tuppleize()]) + if self.is_market_done(): + self.clear_market() + + if self.verbose_logging: + _log.debug("Make offer Market: {} {} exited in state {}".format(self.market_name, + participant.buyer_seller, + self.state)) + + def collect_offers(self): + self.start_offers() + + def clear_market(self): + price = None + quantity = None + error_code = None + error_message = None + aux = {} + if (self.state in [ACCEPT_ALL_OFFERS, ACCEPT_BUY_OFFERS, ACCEPT_SELL_OFFERS]): + error_code = SHORT_OFFERS + error_message = 'The market {} failed to receive all the expected offers. ' \ + 'The state is {}.'.format(self.market_name, self.state) + elif (self.state != MARKET_DONE): + error_code = BAD_STATE + error_message = 'Programming error in Market class. State of {} and clear market signal arrived. ' \ + 'This represents a logic error.'.format(self.state) + else: + if not self.has_market_formed(): + error_code = NOT_FORMED + error_message = 'The market {} has not received a buy and a sell reservation.'.format(self.market_name) + else: + quantity, price, aux = self.offers.settle() + _log.info("Clearing mixmarket: {} Price: {} Qty: {}".format(self.market_name, price, quantity)) + aux = {} + if price is None or quantity is None: + error_code = NO_INTERSECT + error_message = "Error: The supply and demand curves do not intersect. The market {} failed to clear.".format(self.market_name) + _log.info("Clearing price for Market: {} Price: {} Qty: {}".format(self.market_name, price, quantity)) + timestamp = self._get_time() + timestamp_string = utils.format_timestamp(timestamp) + self.publish(peer='pubsub', + topic=MARKET_CLEAR, + message=[timestamp_string, self.market_name, quantity, price]) + self.publish(peer='pubsub', + topic=MARKET_RECORD, + message=[timestamp_string, self.market_name, quantity, price]) + if error_message is not None: + self.publish(peer='pubsub', + topic=MARKET_ERROR, + message=[timestamp_string, self.market_name, error_code, error_message, aux]) + + def has_market_formed(self): + return self.reservations.has_market_formed() + + def log_market_failure(self, message): + _log.debug(message) + raise MarketFailureError(message) + + def all_satisfied(self, buyer_seller): + are_satisfied = False + if (buyer_seller == BUYER): + are_satisfied = self.reservations.buyer_count() == self.offers.buyer_count() + if (buyer_seller == SELLER): + are_satisfied = self.reservations.seller_count() == self.offers.seller_count() + return are_satisfied + + def _get_time(self): + now = utils.get_aware_utc_now() + return now + diff --git a/services/contrib/MarketServiceAgent/market_service/market_list.py b/services/contrib/MarketServiceAgent/market_service/market_list.py new file mode 100644 index 0000000000..994f544806 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/market_list.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging + +from volttron.platform.agent import utils +from .market import Market + +_log = logging.getLogger(__name__) +utils.setup_logging() + + +class NoSuchMarketError(Exception): + """Base class for exceptions in this module.""" + pass + + +class MarketList(object): + def __init__(self, publish = None, verbose_logging = True): + self.markets = {} + self.publish = publish + self.verbose_logging = verbose_logging + + def make_reservation(self, market_name, participant): + if self.has_market(market_name): + market = self.markets[market_name] + market.make_reservation(participant) + else: + market = Market(market_name, participant, self.publish, self.verbose_logging) + self.markets[market_name] = market + + def make_offer(self, market_name, participant, curve): + market = self.get_market(market_name) + market.make_offer(participant, curve) + + def clear_reservations(self): + self.markets.clear() + + def collect_offers(self): + for market in list(self.markets.values()): + market.collect_offers() + + def get_market(self, market_name): + if self.has_market(market_name): + market = self.markets[market_name] + else: + raise NoSuchMarketError('Market %s does not exist.' % market_name) + return market + + def has_market(self, market_name): + return market_name in self.markets + + def has_market_formed(self, market_name): + market_has_formed = False + if self.has_market(market_name): + market = self.markets[market_name] + market_has_formed = market.has_market_formed() + return market_has_formed + + def send_market_failure_errors(self): + for market in list(self.markets.values()): + # We have already sent unformed market failures + if market.has_market_formed(): + # If the market has not cleared trying to clear it will send an error. + if not market.is_market_done(): + market.clear_market() + + def market_count(self): + return len(self.markets) + + def unformed_market_list(self): + _list = [] + for market in list(self.markets.values()): + if not market.has_market_formed(): + _list.append(market.market_name) + return _list diff --git a/services/contrib/MarketServiceAgent/market_service/market_participant.py b/services/contrib/MarketServiceAgent/market_service/market_participant.py new file mode 100644 index 0000000000..9365bf0cdc --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/market_participant.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from volttron.platform.agent.base_market_agent.buy_sell import BUYER, SELLER + + +class MarketParticipant(object): + def __init__(self, buyer_seller, identity): + self.buyer_seller = buyer_seller + self.identity = identity + if not self.is_buyer() and not self.is_seller(): + raise ValueError('expected either %s or %s, but got %s instead.' % (BUYER, SELLER, buyer_seller)) + + def is_buyer(self): + return self.buyer_seller == BUYER + + def is_seller(self): + return self.buyer_seller == SELLER + diff --git a/services/contrib/MarketServiceAgent/market_service/market_state.py b/services/contrib/MarketServiceAgent/market_service/market_state.py new file mode 100644 index 0000000000..0724714941 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/market_state.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +ACCEPT_RESERVATIONS = 0 + + +class MarketStateMachine(object): + + def __init__(self, market_name, reservation): + self.market_name = market_name + self.market_state = ACCEPT_RESERVATIONS diff --git a/services/contrib/MarketServiceAgent/market_service/offer_manager.py b/services/contrib/MarketServiceAgent/market_service/offer_manager.py new file mode 100644 index 0000000000..01aeec9136 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/offer_manager.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import logging + +from volttron.platform.agent import utils +from volttron.platform.agent.base_market_agent.buy_sell import BUYER +from volttron.platform.agent.base_market_agent.poly_line import PolyLine +from volttron.platform.agent.base_market_agent.poly_line_factory import PolyLineFactory + +_log = logging.getLogger(__name__) +utils.setup_logging() + + +class OfferManager(object): + + def __init__(self): + self._buy_offers = [] + self._sell_offers = [] + self.increment = 100 + + def make_offer(self, buyer_seller, curve): + if (buyer_seller == BUYER): + self._buy_offers.append(curve) + else: + self._sell_offers.append(curve) + + def aggregate_curves(self, buyer_seller): + if (buyer_seller == BUYER): + curve = self._aggregate(self._buy_offers) + else: + curve = self._aggregate(self._sell_offers) + return curve + + def _aggregate(self, collection): +# curve = PolyLineFactory.combine(collection, self.increment) + curve = PolyLineFactory.combine_withoutincrement(collection) + return curve + + def settle(self): + enough_buys = len(self._buy_offers) > 0 + enough_sells = len(self._sell_offers) > 0 + if enough_buys: + demand_curve = self._aggregate(self._buy_offers) + else: + _log.debug("There are no buy offers.") + if enough_sells: + supply_curve = self._aggregate(self._sell_offers) + else: + _log.debug("There are no sell offers.") + + if enough_buys and enough_sells: + intersection = PolyLine.intersection(demand_curve, supply_curve) + else: + intersection = None, None, {} + + quantity = intersection[0] + price = intersection[1] + aux = PolyLine.compare(demand_curve, supply_curve) + + return quantity, price, aux + + def buyer_count(self): + return len(self._buy_offers) + + def seller_count(self): + return len(self._sell_offers) + diff --git a/services/contrib/MarketServiceAgent/market_service/reservation_manager.py b/services/contrib/MarketServiceAgent/market_service/reservation_manager.py new file mode 100644 index 0000000000..efa0096591 --- /dev/null +++ b/services/contrib/MarketServiceAgent/market_service/reservation_manager.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + + +class MarketReservationError(Exception): + """Base class for exceptions in this module.""" + pass + + +class ReservationManager(object): + + def __init__(self): + self._buy_reservations = {} + self._sell_reservations = {} + + def make_reservation(self, participant): + if (participant.is_buyer()): + self._make_buy_reservation(participant.identity) + else: + self._make_sell_reservation(participant.identity) + + def _make_buy_reservation(self, owner): + self._add_reservation(self._buy_reservations, owner, 'buy') + + def _add_reservation(self, collection, owner, type): + if owner not in collection: + collection[owner] = False + else: + message = 'Market participant {0} made more than a single {1} reservation.'.format(owner, type) + raise MarketReservationError(message) + + def _make_sell_reservation(self, owner): + self._add_reservation(self._sell_reservations, owner, 'sell') + + def take_reservation(self, participant): + if (participant.is_buyer()): + self._take_buy_reservation(participant.identity) + else: + self._take_sell_reservation(participant.identity) + + def _take_buy_reservation(self, owner): + self._take_reservation(self._buy_reservations, owner, 'buy') + + def _take_sell_reservation(self, owner): + self._take_reservation(self._sell_reservations, owner, 'sell') + + def _take_reservation(self, collection, owner, type): + if owner in collection and not collection[owner]: + collection[owner] = True + else: + message = 'Market participant {0} made no {1} reservation.'.format(owner, type) + raise MarketReservationError(message) + + def has_market_formed(self): + has_buyer = len(self._buy_reservations) > 0 + has_seller = len(self._sell_reservations) > 0 + return has_buyer and has_seller + + def buyer_count(self): + return len(self._buy_reservations) + + def seller_count(self): + return len(self._sell_reservations) + diff --git a/services/contrib/MarketServiceAgent/requirements.txt b/services/contrib/MarketServiceAgent/requirements.txt new file mode 100644 index 0000000000..842facd815 --- /dev/null +++ b/services/contrib/MarketServiceAgent/requirements.txt @@ -0,0 +1,2 @@ +transitions==0.6.9 +numpy==1.15.4 diff --git a/services/contrib/MarketServiceAgent/setup.py b/services/contrib/MarketServiceAgent/setup.py new file mode 100644 index 0000000000..38bbd09f51 --- /dev/null +++ b/services/contrib/MarketServiceAgent/setup.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from os import path +from setuptools import setup, find_packages + +MAIN_MODULE = 'agent' + +# Find the agent package that contains the main module +packages = find_packages('.') +agent_package = '' +for package in find_packages(): + # Because there could be other packages such as tests + if path.isfile(package + '/' + MAIN_MODULE + '.py') is True: + agent_package = package +if not agent_package: + raise RuntimeError('None of the packages under {dir} contain the file ' + '{main_module}'.format(main_module=MAIN_MODULE + '.py', + dir=path.abspath('.'))) + +# Find the version number from the main module +agent_module = agent_package + '.' + MAIN_MODULE +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) +__version__ = _temp.__version__ + +# Setup +setup( + name=agent_package + 'agent', + version=__version__, + install_requires=['volttron', "numpy>1.8,<2", "transitions"], + packages=packages, + entry_points={ + 'setuptools.installation': [ + 'eggsecutable = ' + agent_module + ':main', + ] + } +) diff --git a/services/contrib/MarketServiceAgent/tests/test_market.py b/services/contrib/MarketServiceAgent/tests/test_market.py new file mode 100644 index 0000000000..b0d240ea95 --- /dev/null +++ b/services/contrib/MarketServiceAgent/tests/test_market.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import pytest +try: + from market_service.market_participant import MarketParticipant + from market_service.market import Market, ACCEPT_RESERVATIONS + from volttron.platform.agent.base_market_agent.buy_sell import BUYER, SELLER +except ImportError: + pytest.skip("Market service requirements not installed.", allow_module_level=True) + + +@pytest.mark.market +def test_market_state_create_name(): + market_name = 'test_market' + market = build_test_machine(market_name, BUYER) + assert market_name == market.market_name + + +def build_test_machine(market_name='test_market', buyer_seller = BUYER): + participant = MarketParticipant(buyer_seller, 'agent_id') + publisher = Publisher() + market = Market(market_name, participant, publisher.publish) + return market + + +@pytest.mark.market +def test_market_state_create_state(): + market = build_test_machine() + assert market.state == ACCEPT_RESERVATIONS + + +@pytest.mark.market +def test_market_state_create_has_formed_false(): + market = build_test_machine() + assert market.has_market_formed() is False + + +@pytest.mark.market +def test_market_state_create_has_formed_true(): + market_name = 'test_market' + market = build_test_machine(market_name, BUYER) + participant = MarketParticipant(SELLER, 'agent_id2') + market.make_reservation(participant) + assert market.has_market_formed() + + +class Publisher(object): + def __init__(self): + pass + + def publish(self, peer, topic, headers=None, message=None, bus=''): + pass diff --git a/services/contrib/MarketServiceAgent/tests/test_market_list.py b/services/contrib/MarketServiceAgent/tests/test_market_list.py new file mode 100644 index 0000000000..3255ef248d --- /dev/null +++ b/services/contrib/MarketServiceAgent/tests/test_market_list.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +""" +Pytest test cases for testing market service agent. +""" + +import pytest + +try: + from market_service.market_list import MarketList + from market_service.market_participant import MarketParticipant + from volttron.platform.agent.base_market_agent.buy_sell import BUYER, SELLER +except ImportError: + pytest.skip("Market service requirements not installed.", allow_module_level=True) + + + +@pytest.mark.market +def test_market_participants_no_market(): + market_list = MarketList() + assert market_list.has_market('no_market') is False + + +@pytest.mark.market +def test_market_participants_has_market(): + market_list = MarketList() + market_name = 'test_market' + seller_participant = MarketParticipant(SELLER, 'agent_id') + market_list.make_reservation(market_name, seller_participant) + assert market_list.has_market(market_name) is True + + +@pytest.mark.market +def test_market_participants_market_not_formed_no_market(): + market_list = MarketList() + market_name = 'test_market' + assert market_list.has_market_formed(market_name) is False + + +@pytest.mark.market +def test_market_participants_market_not_formed_one_seller(): + market_list = MarketList() + market_name = 'test_market' + seller_participant = MarketParticipant(SELLER, 'agent_id') + market_list.make_reservation(market_name, seller_participant) + assert market_list.has_market_formed(market_name) is False + + +@pytest.mark.market +def test_market_participants_market_bad_seller_argument(): + market_list = MarketList() + market_name = 'test_market' + with pytest.raises(ValueError) as error_info: + bad_participant = MarketParticipant('bob is cool', 'agent_id') + market_list.make_reservation(market_name, bad_participant) + assert 'bob is cool' in error_info.value.args[0] + + +@pytest.mark.market +def test_market_participants_market_not_formed_one_buyer(): + market_list = MarketList() + market_name = 'test_market' + buyer_participant = MarketParticipant(BUYER, 'agent_id') + market_list.make_reservation(market_name, buyer_participant) + assert market_list.has_market_formed(market_name) == False + + +@pytest.mark.market +def test_market_participants_market_formed_one_buyer_one_seller(): + market_list = MarketList() + market_name = 'test_market' + buyer_participant = MarketParticipant(BUYER, 'agent_id1') + market_list.make_reservation(market_name, buyer_participant) + seller_participant = MarketParticipant(SELLER, 'agent_id2') + market_list.make_reservation(market_name, seller_participant) + assert market_list.market_count() == 1 + assert market_list.has_market_formed(market_name) == True + unformed_markets = market_list.unformed_market_list() + assert len(unformed_markets) == 0 + + +@pytest.mark.market +def test_market_unformed_market_list(): + market_list = MarketList() + market_name1 = 'test_market1' + market_name2 = 'test_market2' + buyer_participant = MarketParticipant(BUYER, 'agent_id1') + market_list.make_reservation(market_name1, buyer_participant) + seller_participant = MarketParticipant(SELLER, 'agent_id2') + market_list.make_reservation(market_name2, seller_participant) + assert market_list.market_count() == 2 + unformed_markets = market_list.unformed_market_list() + assert len(unformed_markets) > 0 diff --git a/services/contrib/MarketServiceAgent/tests/test_market_service_agent.py b/services/contrib/MarketServiceAgent/tests/test_market_service_agent.py new file mode 100644 index 0000000000..e97bbe10f4 --- /dev/null +++ b/services/contrib/MarketServiceAgent/tests/test_market_service_agent.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import os +import json +import gevent +import pytest + +from market_service.market_participant import MarketParticipant +from volttron.platform.agent.base_market_agent.buy_sell import BUYER +from volttron.platform.messaging.health import STATUS_GOOD +from volttron.platform import get_services_core + +""" +Integration tests for Market Service Agent +""" + + +@pytest.mark.market +def test_default_config(volttron_instance): + """ + Test the default configuration file included with the agent + """ + publish_agent = volttron_instance.build_agent(identity="test_agent") + gevent.sleep(1) + + config_path = os.path.join(get_services_core("MarketServiceAgent"), "config") + with open(config_path, "r") as config_file: + config_json = json.load(config_file) + assert isinstance(config_json, dict) + volttron_instance.install_agent( + agent_dir=get_services_core("MarketServiceAgent"), + config_file=config_json, + start=True, + vip_identity="health_test") + assert publish_agent.vip.rpc.call("health_test", "health.get_status").get(timeout=10).get('status') == STATUS_GOOD + + # perform basic sanity check + market_name = 'test_market' + buyer_participant = MarketParticipant(BUYER, 'agent_id1') + + publish_agent.vip.rpc.call("health_test", "make_reservation", market_name, buyer_participant.buyer_seller) diff --git a/services/contrib/MarketServiceAgent/tests/test_offer.py b/services/contrib/MarketServiceAgent/tests/test_offer.py new file mode 100644 index 0000000000..c51c29520d --- /dev/null +++ b/services/contrib/MarketServiceAgent/tests/test_offer.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import pytest +try: + from volttron.platform.agent.base_market_agent.point import Point + from volttron.platform.agent.base_market_agent.poly_line import PolyLine + from volttron.platform.agent.base_market_agent.buy_sell import BUYER, SELLER + from market_service.offer_manager import OfferManager +except ImportError: + pytest.skip("Market service requirements not installed.", allow_module_level=True) + + +@pytest.mark.market +def test_offer_settle_no_intersection(): + demand1 = create_demand_curve() + demand2 = create_demand_curve() + offer_manager = OfferManager() + offer_manager.make_offer(BUYER, demand1) + offer_manager.make_offer(SELLER, demand2) + quantity, price, aux = offer_manager.settle() + assert len(aux) == 4 + +def create_demand_curve(): + demand_curve = PolyLine() + price = 0 + quantity = 1000 + demand_curve.add(Point(price, quantity)) + price = 1000 + quantity = 0 + demand_curve.add(Point(price, quantity)) + return demand_curve diff --git a/services/unsupported/MongodbHistorian/README.md b/services/unsupported/MongodbHistorian/README.md new file mode 100644 index 0000000000..ce2fbd39de --- /dev/null +++ b/services/unsupported/MongodbHistorian/README.md @@ -0,0 +1,174 @@ +# Mongo Historian + +This is a historian that stores its data in mongodb. Data is store in +three different collection as + +1. raw data +2. hourly grouped/rolled up data +3. daily grouped/rolled up data + +The hourly_data and daily_data collections store data grouped together +by time to allow for faster queries. It does not aggregate data (point +values). Data gets loaded into hourly and daily collections through a +periodic batch process and hence might be lagging behind when compared +to raw data collection. The lag time would depend on the load on the +system and hence needs to be set in the configuration. Query API of +mongo historian is designed to handle this. It will combine results from +rollup data and raw data table as needed. + +## Prerequisites + +### 1. Mongodb + +Setup mongodb based on using one of the three below scripts. + +1. Install as root on Redhat or Cent OS + ``` + sudo scripts/historian-scripts/root_install_mongo_rhel.sh + ``` + The above script will prompt user for os version, db user name, + password and database name. Once installed you can start and stop + the service using the command: + ``` + sudo service mongod \[start\|stop\|service\] + ``` + +2. Install as root on Ubuntu + ``` + sudo scripts/historian-scripts/root_install_mongo_ubuntu.sh + ``` + + The above script will prompt user for os version, db user name, + password and database name. Once installed you can start and stop + the service using the command: + ``` + sudo service mongod \[start\|stop\|service\] + ``` +3. Install as non root user on any Linux machine + ``` + scripts/historian-scripts/install_mongodb.sh + ``` + Usage: + ``` + install_mongodb.sh \[-h\] \[-d download_url\] \[-i install_dir\] \[-c config_file\] \[-s\] + ``` + + Optional arguments: + + **-s** setup admin user and test collection after install and startup + + **-d** download url. defaults to + + **-i** install_dir. defaults to current_dir/mongo_install + + **-c** config file to be used for mongodb startup. Defaults to default_mongodb.conf in the same directory as this + script. Any datapath mentioned in the config file should already exist and should have write access to the current user + + **-h** print this help message + +### 2. Create database and user + +You also need to pre-create your database in MongoDB before running this +historian. For example, to create a local MongoDB, do the followings in +Mongo shell: + +- Switch to the new database \"volttron_guide\": + ``` + use volttron_guide + ``` + +- Create a new user for \"volttron_guide\": + ``` + db.createUser({user: "admin", pwd: "admin", roles: ["readWrite"] }) + ``` + + +### 3. Mongodb connector + +This historian requires a mongodb connector installed in your activated +volttron environment to talk to mongodb. Please execute the following +from an activated shell in order to install it. + + pip install pymongo + +The Mongodb Historian also requires the following libraries: + + bson, ujson, dateutil + +And install with + + pip install + +## Configuration + + { + #mandatory connection details + "connection": { + "type": "mongodb", + "params": { + "host": "localhost", + "port": 27017, + "database": "test_historian", + "user": "historian", + "passwd": "historian" + } + }, + + #run historian in query/read only mode and not to record any data into the + database. Default false. optional + "readonly":false, + + # configuration specific to hourly and daily rollup tables + # new from version 2.0 of mongo historian. Most of these configurations + # would become optional once data collected by earlier version of mongo + # has been batch processed to roll up into hourly and daily + # collections. + + ## configurations related to rollup data creation + + # From when should historian start rolling up data into hourly and daily + # collection. Rolling up this way makes queries more efficient + # datetime in "%Y-%m-%dT%H:%M:%S.%f" format and in UTC. Typically this + # should be a date close to the initial use of newer(>=2.0) version of + # mongo historian. Older data should be rolled up using a separate + # background process(see rollup_data_by_time.py script under + # MongodbHistorian/scripts. Default value = current time at the time of + # historian start up + + "initial_rollup_start_time":"2017-01-01T00:00:00.000000", + + # How long should the historian wait after startup to start + # rolling up raw data into hourly and daily collections. Wait in minutes. + # Default 15 seconds + + "periodic_rollup_initial_wait":0.1, + + # How often should the function to rollup data be called. The process of + # rolling up raw data into hourly and daily collections happens in a + # separate process that is run periodically + # units - minutes. Default 1 minute + + "periodic_rollup_frequency":1, + + ## configuration related to using rolled up data for queries + + # Start time from which hourly and daily rollup tables can be used for + # querying. datetime string in UTC. Format "%Y-%m-%dT%H:%M:%S.%f". Default + # current time (at init of historian) + 1day + + "rollup_query_start":"2017-01-01T00:00:00.000000", + + # number of days before current time, that can be used as end + # date for queries from hourly or daily data collections. This is to + # account for the time it takes the periodic_rollup to process + # records in data table and insert into daily_data and hourly_data + # collection. Units days. Default 1 day + + "rollup_query_end":5, + + # topic name patterns for which rollup exists. Set this if rollup was done + # for only a subset of topics + + "rollup_topic_pattern": "^Economizer_RCx|^Airside_RCx" + + } diff --git a/services/unsupported/MongodbHistorian/config b/services/unsupported/MongodbHistorian/config new file mode 100644 index 0000000000..0783d6cdd9 --- /dev/null +++ b/services/unsupported/MongodbHistorian/config @@ -0,0 +1,19 @@ +{ + "connection": { + "type": "mongodb", + "params": { + "host": "localhost", + "port": 27017, + "database": "mongo_test", + "user": "test", + "passwd": "test" + } + }, + "readonly":false, + "initial_rollup_start_time":"2018-01-21T00:00:00.000000", + "periodic_rollup_initial_wait":0.1, + "periodic_rollup_frequency":1, + "rollup_query_start":"2017-01-01T00:00:00.000000", + "rollup_query_end":5, + "rollup_topic_pattern": "^Economizer_RCx|^Airside_RCx" +} diff --git a/services/unsupported/MongodbHistorian/conftest.py b/services/unsupported/MongodbHistorian/conftest.py new file mode 100644 index 0000000000..fd02a0bb5e --- /dev/null +++ b/services/unsupported/MongodbHistorian/conftest.py @@ -0,0 +1,10 @@ +import sys + +from volttrontesting.fixtures.volttron_platform_fixtures import * + +# Add system path of the agent's directory +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) + +# This path update and import shouldn't be needed here but without the below line pytest collection fails with error +# cannot import ALL_TOPIC at line 28 of test_mongohistorian.py +sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(__file__)), "tests")) diff --git a/services/unsupported/MongodbHistorian/mongodb/__init__.py b/services/unsupported/MongodbHistorian/mongodb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/MongodbHistorian/mongodb/historian.py b/services/unsupported/MongodbHistorian/mongodb/historian.py new file mode 100644 index 0000000000..b3f6880826 --- /dev/null +++ b/services/unsupported/MongodbHistorian/mongodb/historian.py @@ -0,0 +1,1119 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + + +import logging +import numbers +import re +import sys +from collections import defaultdict +from datetime import datetime +from datetime import timedelta +from multiprocessing.pool import ThreadPool +from itertools import repeat + +import pymongo +import pytz +from bson.objectid import ObjectId +from dateutil.tz import tzutc +from pymongo import ReplaceOne +from pymongo import UpdateOne +from pymongo.errors import BulkWriteError +import gevent + +from volttron.platform.agent import utils +from volttron.platform.agent.base_historian import BaseHistorian +from volttron.platform.agent.utils import get_aware_utc_now +from volttron.platform.dbutils import mongoutils +from volttron.platform.vip.agent import Core +from volttron.platform.scheduling import periodic +from volttron.utils.docs import doc_inherit + +try: + from ujson import dumps, loads + +except ImportError: + from volttron.platform.jsonapi import dumps, loads + +utils.setup_logging() +_log = logging.getLogger(__name__) +__version__ = '2.1.1' +_VOLTTRON_TYPE = '__volttron_type__' + + +def historian(config_path, **kwargs): + """ + This method is called by the :py:func:`mongodb.historian.main` to parse + the passed config file or configuration dictionary object, validate the + configuration entries, and create an instance of MongodbHistorian + + :param config_path: could be a path to a configuration file or can be a + dictionary object + :param kwargs: additional keyword arguments if any + :return: an instance of :py:class:`MongodbHistorian` + """ + if isinstance(config_path, dict): + config_dict = config_path + else: + config_dict = utils.load_config(config_path) + connection = config_dict.get('connection', None) + assert connection is not None + + database_type = connection.get('type', None) + assert database_type is not None + + params = connection.get('params', None) + assert params is not None + + MongodbHistorian.__name__ = 'MongodbHistorian' + utils.update_kwargs_with_config(kwargs, config_dict) + return MongodbHistorian(**kwargs) + + +class MongodbHistorian(BaseHistorian): + """ + Historian that stores the data into mongodb collections. + + """ + + def __init__(self, connection, tables_def=None, + initial_rollup_start_time=None, rollup_query_start=None, + rollup_topic_pattern=None, rollup_query_end=1, + periodic_rollup_frequency=1, + periodic_rollup_initial_wait=0.25, **kwargs): + + """ + Initialise the historian. + + The historian makes a mongoclient connection to the mongodb server. + This connection is thread-safe and therefore we create it before + starting the main loop of the agent. + + In addition, the topic_map and topic_meta are used for caching meta + data and topics respectively. + + :param connection: dictionary that contains necessary information to + establish a connection to the mongo database. The dictionary should + contain two entries - + + 1. 'type' - describe the type of database and + 2. 'params' - parameters for connecting to the database. + :param tables_def: optional parameter. dictionary containing the + names to be used for historian tables. Should contain the following + keys + + 1. "table_prefix": - if specified tables names are prefixed with + this value followed by a underscore + 2."data_table": name of the table that stores historian data, + 3."topics_table": name of the table that stores the list of topics + for which historian contains data data + 4. "meta_table": name of the table that stores the metadata data + for topics + :param initial_rollup_start_time: + :param rollup_query_start: + :param rollup_topic_pattern: + :param rollup_query_end: + :param periodic_rollup_frequency: + :param periodic_rollup_initial_wait: + :param kwargs: additional keyword arguments. + """ + + tables_def, table_names = self.parse_table_def(tables_def) + self._data_collection = table_names['data_table'] + self._meta_collection = table_names['meta_table'] + self._topic_collection = table_names['topics_table'] + self._agg_topic_collection = table_names['agg_topics_table'] + self._agg_meta_collection = table_names['agg_meta_table'] + self._connection_params = connection['params'] + self._client = None + + self._topic_id_map = {} + self._topic_name_map = {} + self._topic_meta = {} + self._agg_topic_id_map = {} + _log.debug("version number is {}".format(__version__)) + self.version_nums = __version__.split(".") + self.DAILY_COLLECTION = "daily_data" + self.HOURLY_COLLECTION = "hourly_data" + + try: + self._initial_rollup_start_time = get_aware_utc_now() + if initial_rollup_start_time: + self._initial_rollup_start_time = datetime.strptime( + initial_rollup_start_time, + '%Y-%m-%dT%H:%M:%S.%f').replace(tzinfo=pytz.utc) + + # Date from which rolled up data exists in hourly_data and + # daily_data collection + self.rollup_query_start = get_aware_utc_now() + timedelta(days=1) + if rollup_query_start: + self.rollup_query_start = datetime.strptime( + rollup_query_start, + '%Y-%m-%dT%H:%M:%S.%f').replace(tzinfo=pytz.utc) + + # topic_patterns for which queries can be run against rolled up + # tables. This is needed only till batch processing of older data + # is complete and everything gets loaded into hourly and daily + # collections + self.topics_rolled_up = None + try: + if rollup_topic_pattern: + self.topics_rolled_up = re.compile(rollup_topic_pattern) + except Exception as e: + _log.error( + "Invalid rollup_topic_pattern in configuration. {} is " + "not a valid regular expression. " + "\nException: {} ".format(rollup_topic_pattern, e.args)) + + # number of days before current time, that can be used as end + # date for queries from hourly or daily data collections. This is + # to account for the time it takes the periodic_rollup to process + # records in data table and insert into daily_data and hourly_data + # collection + self.rollup_query_end = 1 # default 1 day + if rollup_query_end is not None: + self.rollup_query_end = float(rollup_query_end) + + # how many minutes once should the periodic rollup function be run + self.periodic_rollup_frequency = 60 # default 1 minute + if periodic_rollup_frequency is not None: + self.periodic_rollup_frequency = \ + float(periodic_rollup_frequency) * 60 + + # Number of minutes to wait before calling the periodic_rollup + # function for the first time + # by default wait for 15 seconds + # before running the periodic_rollup for the first time. + self.periodic_rollup_initial_wait = 15 + if periodic_rollup_initial_wait is not None: + self.periodic_rollup_initial_wait = float( + periodic_rollup_initial_wait) * 60 + + # Done with all init call super.init + super(MongodbHistorian, self).__init__(**kwargs) + except ValueError as e: + _log.error("Error processing configuration: {}".format(e)) + return + + @Core.receiver("onstart") + def starting_mongo(self, sender, **kwargs): + _log.debug("In on start method. scheduling periodic call to rollup " + "data") + if not self._readonly: + delay = timedelta(seconds=self.periodic_rollup_initial_wait) + self.core.schedule(periodic(self.periodic_rollup_frequency, + start=delay), + self.periodic_rollup) + + @Core.receiver("onstop") + def closing_mongo_client(self, sender, **kwargs): + if self._client: + _log.debug("Closing mongo client explicitly") + self._client.close() + + def periodic_rollup(self): + _log.info("periodic attempt to do hourly and daily rollup.") + if self._client is None: + _log.debug("historian setup not complete. " + "wait for next periodic call") + return + # Find the records that needs to be processed from data table + db = self._client.get_default_database() + stat = {} + stat["last_data_into_daily"] = self.get_last_updated_data( + db, self.DAILY_COLLECTION) + + stat["last_data_into_hourly"] = self.get_last_updated_data( + db, self.HOURLY_COLLECTION) + find_condition = {} + if stat["last_data_into_daily"]: + find_condition['_id'] = {'$gt': stat["last_data_into_daily"]} + if stat["last_data_into_hourly"]: + if stat["last_data_into_daily"] and stat[ + "last_data_into_hourly"] < \ + stat["last_data_into_daily"]: + find_condition['_id'] = {'$gt': stat["last_data_into_hourly"]} + if not stat["last_data_into_hourly"] and not stat[ + "last_data_into_daily"]: + stat = {} + find_condition['ts'] = {'$gte': self._initial_rollup_start_time} + _log.info("ROLLING FROM start date {}".format( + self._initial_rollup_start_time)) + else: + _log.info("ROLLING FROM last processed id {}".format( + find_condition['_id'])) + + _log.debug("query condition is {} ".format(find_condition)) + + # Iterate and append to a bulk_array. Insert in batches of 1000 + bulk_publish_hour = [] + bulk_publish_day = [] + hour_ids = [] + day_ids = [] + h = 0 + d = 0 + last_topic_id = '' + last_date = '' + cursor = db[self._data_collection].find( + find_condition).sort("_id", pymongo.ASCENDING) + _log.debug("rollup query returned. Looping through to update db") + for row in cursor: + if not stat or row['_id'] > stat["last_data_into_hourly"]: + self.initialize_hourly(topic_id=row['topic_id'], ts=row['ts']) + bulk_publish_hour.append( + MongodbHistorian.insert_to_hourly(db, + row['_id'], + topic_id=row['topic_id'], + ts=row['ts'], + value=row['value'])) + hour_ids.append(row['_id']) + h += 1 + + if not stat or row['_id'] > stat["last_data_into_daily"]: + self.initialize_daily(topic_id=row['topic_id'], + ts=row['ts']) + bulk_publish_day.append( + MongodbHistorian.insert_to_daily(db, + row['_id'], + topic_id=row['topic_id'], + ts=row['ts'], value=row['value'])) + day_ids.append(row['_id']) + d += 1 + # Perform insert if we have 5000 rows + d_errors = h_errors = False + if h == 5000: + bulk_publish_hour, hour_ids, h_errors = \ + MongodbHistorian.bulk_write_rolled_up_data( + self.HOURLY_COLLECTION, bulk_publish_hour, + hour_ids, db) + h = 0 + if d == 5000: + bulk_publish_day, day_ids, d_errors = \ + MongodbHistorian.bulk_write_rolled_up_data( + self.DAILY_COLLECTION, bulk_publish_day, day_ids, db) + d = 0 + if d_errors or h_errors: + # something failed in bulk write. try from last err + # row during the next periodic call + _log.warning("bulk publish errors. last_processed_data would " + "have got recorded in collection. returning from " + "periodic call to try again during next scheduled " + "call") + return + gevent.sleep(0.2) + + # Perform insert for any pending records + if bulk_publish_hour: + _log.debug("bulk_publish outside loop") + MongodbHistorian.bulk_write_rolled_up_data( + self.HOURLY_COLLECTION, bulk_publish_hour, hour_ids, db) + if bulk_publish_day: + _log.debug("bulk_publish outside loop") + MongodbHistorian.bulk_write_rolled_up_data( + self.DAILY_COLLECTION, bulk_publish_day, day_ids, db) + + def get_last_updated_data(self, db, collection): + id = "" + cursor = db[collection].find({}).sort( + "last_updated_data", pymongo.DESCENDING).limit(1) + for row in cursor: + id = row.get('last_updated_data') + return id + + @staticmethod + def bulk_write_rolled_up_data(collection_name, requests, ids, db): + ''' + Handle bulk inserts into daily or hourly roll up table. + :param collection_name: name of the collection on which the bulk + operation should happen + :param requests: array of bulk write requests + :param ids: array of data collection _ids that are part of the bulk + write requests + :param db: handle to database + :return: emptied request array, ids array, and True if there were + errors during write operation or False if there was none + ''' + errors = False + try: + db[collection_name].bulk_write(requests, ordered=True) + except BulkWriteError as ex: + _log.error(str(ex.details)) + errors = True + + else: + ids = [] + requests = [] + return requests, ids, errors + + def version(self): + return __version__ + + def initialize_hourly(self, topic_id, ts): + ts_hour = ts.replace(minute=0, second=0, microsecond=0) + + db = self._client.get_default_database() + # use update+upsert instead of insert cmd as the external script + # to back fill data could have initialized this same row + db[self.HOURLY_COLLECTION].update_one( + {'ts': ts_hour, 'topic_id': topic_id}, + {"$setOnInsert": {'ts': ts_hour, + 'topic_id': topic_id, + 'count': 0, + 'sum': 0, + 'data': [[]] * 60, + 'last_updated_data': ''} + }, + upsert=True) + + def initialize_daily(self, topic_id, ts): + ts_day = ts.replace(hour=0, minute=0, second=0, microsecond=0) + + db = self._client.get_default_database() + db[self.DAILY_COLLECTION].update_one( + {'ts': ts_day, 'topic_id': topic_id}, + {"$setOnInsert": {'ts': ts_day, + 'topic_id': topic_id, + 'count': 0, + 'sum': 0, + 'data': [[]] * 24 * 60, + 'last_updated_data': ''}}, + upsert=True) + + @staticmethod + def insert_to_hourly(db, data_id, topic_id, ts, value): + sum_value = MongodbHistorian.value_to_sumable(value) + rollup_hour = ts.replace(minute=0, second=0, microsecond=0) + + return UpdateOne({'ts': rollup_hour, 'topic_id': topic_id}, + {'$push': {"data." + str(ts.minute): [ts, value]}, + '$inc': {'count': 1, 'sum': sum_value}, + '$set': {'last_updated_data': data_id}}) + + @staticmethod + def insert_to_daily(db, data_id, topic_id, ts, value): + rollup_day = ts.replace(hour=0, minute=0, second=0, + microsecond=0) + position = ts.hour * 60 + ts.minute + sum_value = MongodbHistorian.value_to_sumable(value) + + one = UpdateOne({'ts': rollup_day, 'topic_id': topic_id}, + {'$push': {"data." + str(position): [ts, value]}, + '$inc': {'count': 1, 'sum': sum_value}, + '$set': {'last_updated_data': data_id}}) + return one + + @doc_inherit + def publish_to_historian(self, to_publish_list): + _log.debug("publish_to_historian number of items: {}".format( + len(to_publish_list))) + + # Use the db instance to insert/update the topics + # and data collections + db = self._client.get_default_database() + + bulk_publish = db[self._data_collection].initialize_ordered_bulk_op() + + for x in to_publish_list: + ts = x['timestamp'] + topic = x['topic'] + value = x['value'] + meta = x['meta'] + source = x['source'] + + if source == 'scrape': + source = 'devices' + + # look at the topics that are stored in the database already + # to see if this topic has a value + topic_lower = topic.lower() + topic_id = self._topic_id_map.get(topic_lower, None) + db_topic_name = self._topic_name_map.get(topic_lower, None) + + if topic_id is None: + row = db[self._topic_collection].insert_one( + {'topic_name': topic}) + topic_id = row.inserted_id + self._topic_id_map[topic_lower] = topic_id + self._topic_name_map[topic_lower] = topic + + elif db_topic_name != topic: + _log.debug('Updating topic: {}'.format(topic)) + + result = db[self._topic_collection].update_one( + {'_id': ObjectId(topic_id)}, + {'$set': {'topic_name': topic}}) + assert result.matched_count + self._topic_name_map[topic_lower] = topic + + old_meta = self._topic_meta.get(topic_id, {}) + if set(old_meta.items()) != set(meta.items()): + _log.debug( + 'Updating meta for topic: {} {}'.format(topic, meta)) + db[self._meta_collection].insert_one( + {'topic_id': topic_id, 'meta': meta}) + self._topic_meta[topic_id] = meta + + if isinstance(value, dict): + # Do this so that we need not worry about dict keys with $ or . + value_str = dumps(value) + # create a dict with __volttron_type__ so we can do + # loads() when we query for this data + value = {_VOLTTRON_TYPE: 'json', + 'string_value': value_str} + + bulk_publish.find( + {'ts': ts, 'topic_id': topic_id}).upsert().replace_one( + {'ts': ts, 'topic_id': topic_id, 'source': source, + 'value': value}) + + try: + result = bulk_publish.execute() + except BulkWriteError as bwe: + _log.error("Error during bulk write to data: {}".format( + bwe.details)) + if bwe.details['writeErrors']: + index = bwe.details['writeErrors'][0]['index'] + if index > 0: + _log.debug( + "bulk operation processed {} records before " + "failing".format(bwe.details['writeErrors'][0]['index'])) + self.report_handled(to_publish_list[0:index]) + else: # No write errros here when + self.report_all_handled() + + @staticmethod + def value_to_sumable(value): + # Handle the case where value is not a number so we don't + # increment the sum for that instance. + if isinstance(value, numbers.Number) and not isinstance(value, bool): + sum_value = value + else: + sum_value = 0 + return sum_value + + def query_historian(self, topic, start=None, end=None, agg_type=None, + agg_period=None, skip=0, count=None, + order="FIRST_TO_LAST"): + """ Returns the results of the query from the mongo database. + + This historian stores data to the nearest second. It will not + store subsecond resolution data. This is an optimisation based + upon storage for the database. + Please see + :py:meth:`volttron.platform.agent.base_historian.BaseQueryHistorianAgent.query_historian` + for input parameters and return value details + """ + start_time = datetime.utcnow() + collection_name = self._data_collection + use_rolled_up_data = False + query_start = start + query_end = end + topics_list = [] + if isinstance(topic, str): + topics_list.append(topic) + elif isinstance(topic, list): + topics_list = topic + + if agg_type and agg_period: + # query aggregate data collection instead + collection_name = agg_type + "_" + agg_period + else: + name, query_start, query_end = \ + self.verify_use_of_rolledup_data(start, end, topics_list) + if name: + collection_name = name + use_rolled_up_data = True + _log.debug("Using collection {} for query:".format(collection_name)) + multi_topic_query = len(topics_list) > 1 + topic_ids = [] + id_name_map = {} + for topic in topics_list: + # find topic if based on topic table entry + topic_id = self._topic_id_map.get(topic.lower(), None) + + if agg_type: + agg_type = agg_type.lower() + # replace id from aggregate_topics table + topic_id = self._agg_topic_id_map.get( + (topic.lower(), agg_type, agg_period), None) + if topic_id is None: + # load agg topic id again as it might be a newly + # configured aggregation + self._agg_topic_id_map = mongoutils.get_agg_topic_map( + self._client, self._agg_topic_collection) + topic_id = self._agg_topic_id_map.get( + (topic.lower(), agg_type, agg_period), None) + if topic_id: + topic_ids.append(topic_id) + id_name_map[ObjectId(topic_id)] = topic + else: + _log.warning('No such topic {}'.format(topic)) + + if not topic_ids: + return {} + else: + _log.debug("Found topic id for {} as {}".format( + topics_list, topic_ids)) + + order_by = 1 + if order == 'LAST_TO_FIRST': + order_by = -1 + + if count is None: + count = 100 + skip_count = 0 + if skip > 0: + skip_count = skip + + values = defaultdict(list) + pool = ThreadPool(5) + + try: + + # Query for one topic at a time in a loop instead of topic_id + # $in in order to apply $limit to each topic searched instead + # of the combined result + _log.debug("Spawning threads") + zipped_args = list(zip(topic_ids, repeat(id_name_map), + repeat(collection_name), repeat(start), + repeat(end), repeat(query_start), repeat(query_end), + repeat(count), repeat(skip_count), repeat(order_by), + repeat(use_rolled_up_data), + repeat(values))) + pool.starmap(self.query_topic_data, zipped_args) + pool.close() + pool.join() + _log.debug("Time taken to load all values for all topics" + " {}".format(datetime.utcnow() - start_time)) + # _log.debug("Results got {}".format(values)) + + return self.add_metadata_to_query_result(agg_type, + multi_topic_query, + topic, + topic_ids, + values) + + finally: + pool.close() + + def query_topic_data(self, topic_id, id_name_map, collection_name, start, + end, query_start, query_end, count, skip_count, + order_by, use_rolled_up_data, values): + start_time = datetime.utcnow() + topic_name = id_name_map[topic_id] + db = self._client.get_default_database() + + find_params = {} + ts_filter = {} + if query_start is not None: + ts_filter["$gte"] = query_start + + if query_end is not None: + ts_filter["$lt"] = query_end + + if ts_filter: + if start == end: + find_params = {'ts': start} + else: + find_params = {'ts': ts_filter} + + find_params['topic_id'] = ObjectId(topic_id) + _log.debug("{}:Querying topic {}".format(topic_id, topic_id)) + raw_data_project = {"_id": 0, "timestamp": { + '$dateToString': {'format': "%Y-%m-%dT%H:%M:%S.%L000+00:00", + "date": "$ts"}}, "value": 1} + if use_rolled_up_data: + project = {"_id": 0, "data": 1} + else: + project = {"_id": 0, "timestamp": { + '$dateToString': {'format': "%Y-%m-%dT%H:%M:%S.%L000+00:00", + "date": "$ts"}}, "value": 1} + pipeline = [{"$match": find_params}, {"$skip": skip_count}, + {"$sort": {"ts": order_by}}, {"$limit": count}, + {"$project": project}] + _log.debug("{}:pipeline for querying {} is {}".format( + topic_id, collection_name, pipeline)) + cursor = db[collection_name].aggregate(pipeline) + rows = list(cursor) + _log.debug("{}:Time after fetch {}".format( + topic_id, datetime.utcnow() - start_time)) + if use_rolled_up_data: + for row in rows: + if order_by == 1: + for minute_data in row['data']: + if minute_data: + # there could be more than data entry during the + # same minute + for data in minute_data: + self.update_values(data, topic_name, start, + end, values) + else: + for minute_data in reversed(row['data']): + if minute_data: + # there could be more than data entry during the + # same minute + for data in reversed(minute_data): + self.update_values(data, topic_name, start, + end, + values) + _log.debug( + "{}:number of records from rolled up " + "collection is {}".format(topic_id, len(values[topic_name]))) + check_count = False + if query_start > start: + if len(values[topic_name]) == count and order_by == -1: + # if order by descending and count is already met, + # nothing to do + _log.debug("{}:Count limit already met. do not query raw " + "data".format(topic_id)) + else: + # query raw data collection for rest of the dates + find_params['ts'] = {'$gte': start, + '$lt': self.rollup_query_start} + pipeline = [{"$match": find_params}, {"$skip": skip_count}, + {"$sort": {"ts": order_by}}, {"$limit": count}, + {"$project": raw_data_project}] + self.add_raw_data_results(db, topic_name, values, + pipeline, order_by == 1) + check_count = True + if query_end < end: + if len(values[topic_name]) == count and order_by == 1: + # if order by ascending and count is already met, + # nothing to do + _log.debug("{}:Count limit already met. do not query raw " + "data".format(topic_id)) + else: + # query raw data collection for rest of the dates + find_params['ts'] = {'$gte': query_end, + '$lt': end} + pipeline = [{"$match": find_params}, {"$skip": skip_count}, + {"$sort": {"ts": order_by}}, {"$limit": count}, + {"$project": raw_data_project}] + self.add_raw_data_results(db, topic_name, values, + pipeline, order_by == -1) + check_count = True + + if check_count: + # Check if count has increased after adding raw data + # trim if needed + if len(values[topic_name]) > count: + _log.debug("{}:result count exceeds limit".format( + topic_id, len(values[topic_name]))) + values[topic_name] == values[topic_name][:count] + + else: + for row in rows: + result_value = self.json_string_to_dict(row['value']) + values[topic_name].append( + (row['timestamp'], result_value)) + _log.debug( + "{}:loading results only from raw data collections. " + "Results length {}".format(topic_id, len(values[topic_name]))) + + _log.debug("{}:Time taken to load results: {}".format( + topic_id, datetime.utcnow() - start_time)) + + def add_raw_data_results(self, db, topic_name, values, pipeline, + add_to_beginning): + + _log.debug("pipeline for querying raw data is {}".format(pipeline)) + cursor = db[self._data_collection].aggregate(pipeline) + rows = list(cursor) + _log.debug("number of raw data records {}".format(len(rows))) + new_values = defaultdict(list) + for row in rows: + result_value = self.json_string_to_dict(row['value']) + new_values[topic_name].append( + (row['timestamp'], result_value)) + # add to results from rollup collections + if add_to_beginning: + # add to beginning + _log.debug("adding to beginning") + new_values.get(topic_name, []).extend(values.get(topic_name, [])) + values[topic_name] = new_values.get(topic_name, []) + else: + # add to end + _log.debug("adding to end") + values.get(topic_name, []).extend(new_values.get(topic_name, [])) + values[topic_name] = values.get(topic_name, []) + + def update_values(self, data, topic_name, start, end, values): + if start.tzinfo: + data[0] = data[0].replace(tzinfo=tzutc()) + if data[0] >= start and data[0] < end: + result_value = self.json_string_to_dict(data[1]) + values[topic_name].append( + (utils.format_timestamp(data[0]), result_value)) + + def json_string_to_dict(self, value): + """ + Verify if the value was converted to json string at the time of + storing into db. If so, convert it back to dict and return + :param value: + :return: + """ + result_value = value + if isinstance(result_value, dict) and result_value.get(_VOLTTRON_TYPE): + if result_value[_VOLTTRON_TYPE] == 'json': + result_value = loads(result_value['string_value']) + return result_value + + def verify_use_of_rolledup_data(self, start, end, topics_list): + """ + See if we can use rolled up data only be done with version >2, + with valid time period verify start is >= from when rolled up data + is available verify end date is < current time - configured lag time + (config.rollup_query_end) this is to account for any lag between + the main historian thread and the thread that periodically rolls up + data. Also check rolled up data exists for the topics queried + :param start: query start time + :param end: query end time + :param topics_list: list of topics in queried + :return: + """ + # + collection_name = "" + query_start = start + query_end = end + # if it is the right version of historian and + # if start and end dates are within the range for which rolled up + # data is available, use hourly_data or monthly_data collection + rollup_end = get_aware_utc_now() - timedelta( + days=self.rollup_query_end) + _log.debug("historian version:{}".format(self.version_nums[0])) + _log.debug("start {} and end {}".format(start, end)) + _log.debug("rollup query start {}".format(self.rollup_query_start)) + _log.debug("rollup query end {}".format(rollup_end)) + if int(self.version_nums[0]) < 2: + return collection_name, query_start, query_end + + match_list = [True] + if self.topics_rolled_up: + match_list = [bool(self.topics_rolled_up.match(t)) for t in + topics_list] + + # For now use rolledup data collections only if all topics in query + # are present in rolled up format. Mainly because this topic_pattern + # match is only a temporary fix. We want all topics to get loaded + # into hourly or daily collections + if not (False in match_list) and start and end and start != end and \ + end > self.rollup_query_start and start < rollup_end: + diff = (end - start).total_seconds() + + if start >= self.rollup_query_start and end < rollup_end: + _log.debug("total seconds between end and start {}".format(diff)) + if diff >= 24 * 3600: + collection_name = self.DAILY_COLLECTION + query_start = start.replace(hour=0, minute=0, second=0, + microsecond=0) + query_end = (end + timedelta(days=1)).replace( + hour=0, minute=0, second=0, microsecond=0) + elif diff >= 3600 * 3: # more than 3 hours of data + collection_name = self.HOURLY_COLLECTION + query_start = start.replace(minute=0, second=0, + microsecond=0) + query_end = (end + timedelta(hours=1)).replace( + minute=0, second=0, microsecond=0) + elif diff >= 24 * 3600: + # if querying more than a day's worth data, get part of data + # of roll up query and rest for raw data + collection_name = self.DAILY_COLLECTION + if start < self.rollup_query_start: + query_start = self.rollup_query_start + query_start = query_start.replace(hour=0, + minute=0, + second=0, + microsecond=0) + else: + query_start = start.replace(hour=0, minute=0, second=0, + microsecond=0) + + if end > rollup_end: + query_end = rollup_end + else: + query_end = (end + timedelta(days=1)).replace(hour=0, + minute=0, second=0, microsecond=0) + + _log.debug("Verify use of rollup data: {}".format(collection_name)) + return collection_name, query_start, query_end + + def add_metadata_to_query_result(self, agg_type, multi_topic_query, + topic, topic_ids, values): + ''' + Adds metadata to query results. If query is based on multiple topics + does not add any metadata. If query is based on single topic return + the metadata of it. If topic is an aggregate topic, returns the + metadata of underlying topic + :param agg_type: + :param multi_topic_query: + :param topic: + :param topic_ids: + :param values: + :return: + ''' + results = dict() + if len(values) > 0: + # If there are results add metadata if it is a query on a + # single + # topic + meta_tid = None + if not multi_topic_query: + values = list(values.values())[0] + + if agg_type: + # if aggregation is on single topic find the topic id + # in the topics table. + # if topic name does not have entry in topic_id_map + # it is a user configured aggregation_topic_name + # which denotes aggregation across multiple points + _log.debug("Single topic aggregate query. Try to get " + "metadata") + meta_tid = self._topic_id_map.get(topic.lower(), None) + else: + # this is a query on raw data, get metadata for + # topic from topic_meta map + meta_tid = topic_ids[0] + if values: + metadata = self._topic_meta.get(meta_tid, {}) + results = {'values': values, 'metadata': metadata} + else: + results = dict() + return results + + @doc_inherit + def query_topic_list(self): + db = self._client.get_default_database() + cursor = db[self._topic_collection].find() + + res = [] + for document in cursor: + res.append(document['topic_name']) + + return res + + @doc_inherit + def query_topics_by_pattern(self, topics_pattern): + _log.debug("In query topics by pattern: {}".format(topics_pattern)) + db = self._client.get_default_database() + topics_pattern = topics_pattern.replace('/', '\/') + pattern = {'topic_name': {'$regex': topics_pattern, '$options': 'i'}} + cursor = db[self._topic_collection].find(pattern) + topic_id_map = dict() + for document in cursor: + topic_id_map[document['topic_name']] = str(document[ + '_id']) + _log.debug("Returning topic map :{}".format(topic_id_map)) + return topic_id_map + + @doc_inherit + def query_topics_metadata(self, topics): + + meta = {} + if isinstance(topics, str): + topic_id = self._topic_id_map.get(topics.lower()) + if topic_id: + meta = {topics: self._topic_meta.get(topic_id)} + elif isinstance(topics, list): + for topic in topics: + topic_id = self._topic_id_map.get(topic.lower()) + if topic_id: + meta[topic] = self._topic_meta.get(topic_id) + return meta + + def query_aggregate_topics(self): + return mongoutils.get_agg_topics( + self._client, + self._agg_topic_collection, + self._agg_meta_collection) + + def _load_topic_map(self): + _log.debug('loading topic map') + db = self._client.get_default_database() + cursor = db[self._topic_collection].find() + + # Hangs when using cursor as iterable. + # See https://github.com/VOLTTRON/volttron/issues/643 + for num in range(cursor.count()): + document = cursor[num] + self._topic_id_map[document['topic_name'].lower()] = document[ + '_id'] + self._topic_name_map[document['topic_name'].lower()] = \ + document['topic_name'] + + def _load_meta_map(self): + _log.debug('loading meta map') + db = self._client.get_default_database() + cursor = db[self._meta_collection].find() + # Hangs when using cursor as iterable. + # See https://github.com/VOLTTRON/volttron/issues/643 + for num in range(cursor.count()): + document = cursor[num] + self._topic_meta[document['topic_id']] = document['meta'] + + @doc_inherit + def historian_setup(self): + _log.debug("HISTORIAN SETUP") + self._client = mongoutils.get_mongo_client(self._connection_params, + minPoolSize=10) + _log.info("Mongo client created with min pool size {}".format( + self._client.min_pool_size)) + db = self._client.get_default_database() + col_list = db.collection_names() + create_index1 = True + create_index2 = True + + if self._readonly: + create_index1 = False + create_index2 = False + # if data collection exists check if necessary indexes exists + elif self._data_collection in col_list: + index_info = db[self._data_collection].index_information() + index_list = [value['key'] for value in index_info.values()] + index_new_list = [] + for index in index_list: + keys = set() + for key in index: + keys.add(key[0]) + index_new_list.append(keys) + + _log.debug("Index list got from db is {}. formatted list is ".format( + index_list, index_new_list)) + i1 = {'topic_id', 'ts'} + if i1 in index_new_list: + create_index1 = False + i2 = {'ts'} + if i2 in index_new_list: + create_index2 = False + + # create data indexes if needed + if create_index1: + db[self._data_collection].create_index( + [('topic_id', pymongo.DESCENDING), + ('ts', pymongo.DESCENDING)], + unique=True, background=True) + if create_index2: + db[self._data_collection].create_index( + [('ts', pymongo.DESCENDING)], background=True) + + self._topic_id_map, self._topic_name_map = \ + mongoutils.get_topic_map( + self._client, self._topic_collection) + self._load_meta_map() + + if self._agg_topic_collection in db.collection_names(): + _log.debug("found agg_topics_collection ") + self._agg_topic_id_map = mongoutils.get_agg_topic_map( + self._client, self._agg_topic_collection) + else: + _log.debug("no agg topics to load") + self._agg_topic_id_map = {} + + if not self._readonly: + db[self.HOURLY_COLLECTION].create_index( + [('topic_id', pymongo.DESCENDING), ('ts', pymongo.DESCENDING)], + unique=True, background=True) + db[self.HOURLY_COLLECTION].create_index( + [('last_updated_data', pymongo.DESCENDING)], background=True) + db[self.DAILY_COLLECTION].create_index( + [('topic_id', pymongo.DESCENDING), ('ts', pymongo.DESCENDING)], + unique=True, background=True) + db[self.DAILY_COLLECTION].create_index( + [('last_updated_data', pymongo.DESCENDING)], + background=True) + + def record_table_definitions(self, meta_table_name): + _log.debug("In record_table_def table:{}".format(meta_table_name)) + + db = self._client.get_default_database() + db[meta_table_name].bulk_write([ + ReplaceOne( + {'table_id': 'data_table'}, + {'table_id': 'data_table', + 'table_name': self._data_collection, 'table_prefix': ''}, + upsert=True), + ReplaceOne( + {'table_id': 'topics_table'}, + {'table_id': 'topics_table', + 'table_name': self._topic_collection, 'table_prefix': ''}, + upsert=True), + ReplaceOne( + {'table_id': 'meta_table'}, + {'table_id': 'meta_table', + 'table_name': self._meta_collection, 'table_prefix': ''}, + upsert=True)]) + + def manage_db_size(self, history_limit_timestamp, storage_limit_gb): + """ + Remove documents older than `history_limit_timestamp` from + all collections when `history_limit_days` is specified in the + agent configuration. `storage_limit_gb` is ignored. + """ + if history_limit_timestamp is None: + return + history_limit_timestamp = history_limit_timestamp.replace(hour=0, + minute=0, + second=0, + microsecond=0) + collection_names = (self._data_collection, + self.HOURLY_COLLECTION, + self.DAILY_COLLECTION) + + db = self._client.get_default_database() + query = {"ts": {"$lt": history_limit_timestamp}} + + for collection_name in collection_names: + db[collection_name].delete_many(query) + + +def main(argv=sys.argv): + """Main method called by the eggsecutable. + @param argv: + """ + try: + utils.vip_main(historian, version=__version__) + except Exception as e: + print(e) + _log.exception('unhandled exception') + + +if __name__ == '__main__': + # Entry point for script + try: + sys.exit(main()) + except KeyboardInterrupt: + pass diff --git a/services/unsupported/MongodbHistorian/requirements.txt b/services/unsupported/MongodbHistorian/requirements.txt new file mode 100644 index 0000000000..be3baf0119 --- /dev/null +++ b/services/unsupported/MongodbHistorian/requirements.txt @@ -0,0 +1,3 @@ +pymongo +bson +ujson diff --git a/services/unsupported/MongodbHistorian/scripts/count_data_by_topic_pattern.py b/services/unsupported/MongodbHistorian/scripts/count_data_by_topic_pattern.py new file mode 100644 index 0000000000..872d39ac6b --- /dev/null +++ b/services/unsupported/MongodbHistorian/scripts/count_data_by_topic_pattern.py @@ -0,0 +1,25 @@ +import re +import datetime +from pymongo import MongoClient +from bson import ObjectId +client = MongoClient("mongodb://reader:volttronReader@172.26.63.4:27017" + "/2017_production_external?authSource=admin") +db = client.get_default_database() +regex = re.compile("^Economizer_RCx|^Airside_RCx", re.IGNORECASE) + +cursor = db['topics'].find({"topic_name": regex}) +ids_dicts = list(cursor) +#ids = [x['_id'] for x in ids_dicts] +#count = db.data.find({"topic_id":{"$in":ids}}).count() +start = datetime.datetime.now() +count = 0 +start_date = '01Jan2016T00:00:00.000' +end_date = '14Mar2017T00:00:00.000' +s_dt = datetime.datetime.strptime(start_date, '%d%b%YT%H:%M:%S.%f') +e_dt = datetime.datetime.strptime(end_date, '%d%b%YT%H:%M:%S.%f') +for x in ids_dicts: + count = count + db.data.find( + {"topic_id":x['_id'], + "ts":{"$gte":s_dt, "$lt":e_dt}}).count() +print (count) +print ("time taken: {}".format(datetime.datetime.now()-start)) \ No newline at end of file diff --git a/services/unsupported/MongodbHistorian/scripts/rollup_data_by_time.py b/services/unsupported/MongodbHistorian/scripts/rollup_data_by_time.py new file mode 100644 index 0000000000..c351d4713d --- /dev/null +++ b/services/unsupported/MongodbHistorian/scripts/rollup_data_by_time.py @@ -0,0 +1,428 @@ +import re + +from gevent import monkey +monkey.patch_all() + +try: + import pymongo +except: + raise Exception("Required: pymongo") +from datetime import datetime +from numbers import Number +from pymongo.errors import BulkWriteError +from gevent.pool import Pool + + +### START - Variables to set before running script ## +local_source_params = {"host": "localhost", + "port": 27017, + "authSource":"mongo_test", + "database": "performance_test", + "user": "test", + "passwd": "test"} + +DAILY_COLLECTION = "daily_data" +HOURLY_COLLECTION = "hourly_data" +log_out_file = "./script_out" +init_rollup_tables = True # Set this to false if init is already done and if +# you are rerunning to script for different date range or topic pattern +start_date = '30Mar2016T00:00:00.000' +end_date = '10Feb2017T00:00:00.000' +topic_patterns = ["^Economizer_RCx|^Airside_RCx", + '^PNNL-SEQUIM', '^pnnl', + '^datalogger', '^record'] +### END - Variables to set before running script ## + +import sys +log = open(log_out_file, 'w', buffering=1) +sys.stdout = log +sys.stderr = log +#log = None + + +def connect_mongodb(connection_params): + + mongo_conn_str = 'mongodb://{user}:{passwd}@{host}:{port}/{database}' + if connection_params.get('authSource'): + mongo_conn_str = mongo_conn_str + '?authSource={authSource}' + params = connection_params + mongo_conn_str = mongo_conn_str.format(**params) + + mongo_client = pymongo.MongoClient(mongo_conn_str) + db = mongo_client[connection_params['database']] + return db + + +def get_table_names(config): + default_table_def = {"table_prefix": "", "data_table": "data", + "topics_table": "topics", "meta_table": "meta"} + tables_def = config.get('tables_def', None) + if not tables_def: + tables_def = default_table_def + table_names = dict(tables_def) + table_names["agg_topics_table"] = "aggregate_" + tables_def["topics_table"] + table_names["agg_meta_table"] = "aggregate_" + tables_def["meta_table"] + + table_prefix = tables_def.get('table_prefix', None) + table_prefix = table_prefix + "_" if table_prefix else "" + if table_prefix: + for key, value in table_names.items(): + table_names[key] = table_prefix + table_names[key] + + return table_names + + +def rollup_data(source_params, dest_params, start_date, end_date, topic_id, + topic_name): + source_db = None + dest_db = None + start = datetime.utcnow() + cursor = None + try: + source_db = connect_mongodb(source_params) + source_tables = get_table_names(source_params) + + dest_db = connect_mongodb(dest_params) + dest_tables = get_table_names(dest_params) + + dest_db[HOURLY_COLLECTION].create_index( + [('topic_id', pymongo.DESCENDING), ('ts', pymongo.DESCENDING)], + unique=True, background=False) + dest_db[HOURLY_COLLECTION].create_index( + [('last_back_filled_data', pymongo.ASCENDING)], background=False) + + dest_db[DAILY_COLLECTION].create_index( + [('topic_id', pymongo.DESCENDING), ('ts', pymongo.DESCENDING)], + unique=True, background=False) + dest_db[DAILY_COLLECTION].create_index( + [('last_back_filled_data', pymongo.ASCENDING)], background=False) + + match_condition = {'ts': {'$gte': start_date, '$lt': end_date}} + match_condition['topic_id'] = topic_id + + stat = {} + stat["last_data_into_daily"] = get_last_back_filled_data( + dest_db, DAILY_COLLECTION, topic_id, topic_name) + + stat["last_data_into_hourly"] = get_last_back_filled_data( + dest_db, HOURLY_COLLECTION, topic_id, topic_name) + + if stat["last_data_into_daily"]: + match_condition['_id'] = {'$gt': stat["last_data_into_daily"]} + if stat["last_data_into_hourly"]: + if stat["last_data_into_daily"] and stat[ + "last_data_into_hourly"] < \ + stat["last_data_into_daily"]: + match_condition['_id'] = {'$gt': stat["last_data_into_hourly"]} + if not stat["last_data_into_hourly"] and not stat[ + "last_data_into_daily"]: + stat = {} + + cursor = source_db[source_tables['data_table']].find( + match_condition, no_cursor_timeout=True).sort( + "_id", pymongo.ASCENDING) + + # Iterate and append to a bulk_array. Insert in batches of 3000 + d = 0 + h = 0 + bulk_hourly = dest_db[HOURLY_COLLECTION].initialize_ordered_bulk_op() + bulk_daily = dest_db[DAILY_COLLECTION].initialize_ordered_bulk_op() + last_init_hour = None + last_init_day = None + for row in cursor: + if not stat or row['_id'] > stat["last_data_into_hourly"]: + # ts_hour = row['ts'].replace(minute=0, second=0, + # microsecond=0) + # if last_init_hour is None or ts_hour != last_init_hour : + # # above check would work since we use 1 thread per topic + # initialize_hourly(topic_id=row['topic_id'], + # ts_hour=ts_hour, + # db=dest_db) + # last_init_hour = ts_hour + insert_to_hourly(bulk_hourly, row['_id'], + topic_id=row['topic_id'], ts=row['ts'], value=row['value']) + h += 1 + #print("Insert bulk op to hourly. h= {}".format(h)) + + if not stat or row['_id'] > stat["last_data_into_daily"]: + # ts_day = row['ts'].replace(hour=0, minute=0, + # second=0, microsecond=0) + # if last_init_day is None or ts_day != last_init_day: + # initialize_daily(topic_id=row['topic_id'], ts_day=ts_day, + # db=dest_db) + # last_init_day = ts_day + insert_to_daily(bulk_daily, row['_id'], + topic_id=row['topic_id'], ts=row['ts'], + value=row['value']) + d += 1 + #print("Insert bulk op to daily d= {}".format(d)) + + # Perform insert if we have 10000 rows + d_errors = h_errors = False + if h == 10000: + #print("In loop. bulk write hour") + h_errors = execute_batch("hourly", bulk_hourly, h, topic_id, + topic_name) + if not h_errors: + bulk_hourly = dest_db[ + HOURLY_COLLECTION].initialize_ordered_bulk_op() + h = 0 + + if d == 10000: + #print("In loop. bulk write day") + d_errors = execute_batch("daily", bulk_daily, d, topic_id, + topic_name) + if not d_errors: + bulk_daily = dest_db[ + DAILY_COLLECTION].initialize_ordered_bulk_op() + d = 0 + + if d_errors or h_errors: + # something failed in bulk write. try from last err + # row during the next periodic call + print( + "error writing into {} data collection for topic: " + "{}:{}".format("hourly" if h_errors else "daily", + topic_id, topic_name)) + return + + # Perform insert for any pending records + if h > 0: + h_errors = execute_batch("hourly", bulk_hourly, h, topic_id, + topic_name) + if h_errors: + print("Error processing data into daily collection. " + "topic {}:{}".format(topic_id, topic_name)) + if d > 0: + d_errors = execute_batch("daily", bulk_daily, d, topic_id, + topic_name) + if d_errors: + print("Error processing data into daily collection. " + "topic {}:{}".format(topic_id, topic_name)) + except Exception as e: + print("Exception processing topic {}:{} {}".format(topic_id, + topic_name, + e.args)) + finally: + if cursor: + cursor.close() + if source_db: + source_db.client.close() + if dest_db: + dest_db.client.close() + + +def get_last_back_filled_data(db, collection, topic_id, topic_name): + id = "" + match_condition = {'topic_id': topic_id} + cursor = db[collection].find(match_condition).sort("last_back_filled_data", + pymongo.DESCENDING).limit(1) + for row in cursor: + id = row.get('last_back_filled_data') + + return id + + +def execute_batch(table_type, bulk, count, topic_id, topic_name): + """ + Execute bulk operation. return true if operation completed successfully + False otherwise + """ + errors = False + try: + result = bulk.execute() + if result['nModified'] != count: + print( + "bulk execute of {} data for {}:{}.\nnumber of op sent to " + "bulk execute ({}) does not match nModified count".format( + table_type, topic_id, topic_name, count)) + print ("bulk execute result {}".format(result)) + errors = True + except BulkWriteError as ex: + print(str(ex.details)) + errors = True + + return errors + + +def insert_to_hourly(bulk_hourly, data_id, topic_id, ts, value): + rollup_hour = ts.replace(minute=0, second=0, microsecond=0) + sum_value = value_to_sumable(value) + bulk_hourly.find({'ts': rollup_hour, 'topic_id': topic_id}).update( + {'$push': {"data." + str(ts.minute): [ts, value]}, + '$inc': {'count': 1, 'sum': sum_value}, + '$set': {'last_back_filled_data': data_id}}) + + +def insert_to_daily(bulk_daily, data_id, topic_id, ts, value): + rollup_day = ts.replace(hour=0, minute=0, second=0, microsecond=0) + position = ts.hour * 60 + ts.minute + sum_value = value_to_sumable(value) + + bulk_daily.find({'ts': rollup_day, 'topic_id': topic_id}).update_one( + {'$push': {"data." + str(position): [ts, value]}, + '$inc': {'count': 1, 'sum': sum_value}, + '$set': {'last_back_filled_data': data_id}}) + + +def value_to_sumable(value): + # Handle the case where value is not a number so we don't + # increment the sum for that instance. + if isinstance(value, Number) and not isinstance(value, bool): + sum_value = value + else: + sum_value = 0 + return sum_value + + +def init_daily_data(db, data_collection, start_dt, end_dt): + pipeline = [] + if start_date and end_date: + pipeline.append({"$match": + {'ts': {"$gte": start_dt, "$lt": end_dt}}}) + pipeline.append({'$group': { + '_id': {'topic_id': "$topic_id", 'year': {"$year": "$ts"}, + 'month': {"$month": "$ts"}, + 'dayOfMonth': {"$dayOfMonth": "$ts"}}, + 'h': {"$first": {"$hour": "$ts"}}, + 'm': {"$first": {"$minute": "$ts"}}, + 's': {"$first": {"$second": "$ts"}}, + 'ml': {"$first": {"$millisecond": "$ts"}}, 'ts': {"$first": "$ts"}, + 'topic_id': {"$first": "$topic_id"}, 'sum': {"$sum": "$sum"}, + 'count': {"$sum": "$count"}}}) + pipeline.append({'$project': {'_id': 0, 'ts': {"$subtract": ["$ts", { + "$add": ["$ml", {"$multiply": ["$s", 1000]}, + {"$multiply": ["$m", 60, 1000]}, + {"$multiply": ["$h", 60, 60, 1000]}]}]}, 'topic_id': 1, + 'sum': {"$literal": 0}, 'count': {"$literal": 0}, + 'data': {"$literal": [[]] * 24 * 60}}}) + + pipeline.append({"$out": DAILY_COLLECTION}) + db[data_collection].aggregate(pipeline, allowDiskUse=True) + + +def init_hourly_data(db, data_collection, start_dt, end_dt): + + pipeline = [] + pipeline.append({"$match": {'ts': {"$gte": start_dt, "$lt": end_dt}}}) + pipeline.append({'$group': { + '_id': {'topic_id': "$topic_id", + 'year': {"$year": "$ts"}, + 'month': {"$month": "$ts"}, + 'dayOfMonth': {"$dayOfMonth": "$ts"}, + 'hour': {"$hour": "$ts"}}, + 'm': {"$first": {"$minute": "$ts"}}, + 's': {"$first": {"$second": "$ts"}}, + 'ml': {"$first": {"$millisecond": "$ts"}}, + 'ts': {"$first": "$ts"}, + 'topic_id': {"$first": "$topic_id"}, + 'sum': {"$sum": "$sum"}, + 'count': {"$sum": "$count"}}}) + pipeline.append({'$project': { + '_id': 0, + 'ts': {"$subtract": [ + "$ts", { + "$add": [ + "$ml", + {"$multiply": ["$s", 1000]}, + {"$multiply": ["$m", 60, 1000]}] + }]}, + 'topic_id': 1, + 'sum': {"$literal": 0}, 'count': {"$literal": 0}, + 'data': {"$literal": [[]] * 60}}}) + + pipeline.append({"$out": HOURLY_COLLECTION}) + db[data_collection].aggregate(pipeline, allowDiskUse=True) + + +if __name__ == '__main__': + start = datetime.utcnow() + + print ("Starting rollup of data from {} to {}. current time: {}".format( + start_date, end_date, start)) + + pool = Pool(size=10) + try: + source_db = connect_mongodb(local_source_params) + source_tables = get_table_names(local_source_params) + init_done = False + if init_rollup_tables: + existing_collections = source_db.collection_names() + if HOURLY_COLLECTION in existing_collections: + print( + "init_rollup_tables set to True and hourly collection " + "name is set as {}. But this collection already exists " + "in the database. Exiting to avoid init process " + "overwriting existing collection {}. Please rename " + "collection in db or change value of " + "HOURLY_COLLECTION in script".format( + HOURLY_COLLECTION, HOURLY_COLLECTION)) + elif DAILY_COLLECTION in existing_collections: + print( + "init_rollup_tables set to True and daily collection " + "name is set as {}. But this collection already exists " + "in the database. Exiting to avoid init process " + "overwriting existing collection {}. Please rename " + "collection in db or change value of " + "DAILY_COLLECTION in script".format( + DAILY_COLLECTION, DAILY_COLLECTION)) + else: + source_db = connect_mongodb(local_source_params) + s_dt = datetime.strptime(start_date, '%d%b%YT%H:%M:%S.%f') + e_dt = datetime.strptime(end_date, '%d%b%YT%H:%M:%S.%f') + print ("Starting init of tables") + init_start = datetime.utcnow() + init_daily_data(source_db, + source_tables['data_table'], + s_dt, + e_dt) + print ("Total time for init of daily data " + "between {} and {} : {} " + "".format(start_date, end_date, + datetime.utcnow() - init_start)) + init_start = datetime.utcnow() + init_hourly_data(source_db, + source_tables['data_table'], + s_dt, + e_dt) + print ("Total time for init of hourly data " + "between {} and {} : {} " + "".format(start_date, end_date, + datetime.utcnow() - init_start)) + init_done = True + else: + init_done = True + + if init_done: + for topic_pattern in topic_patterns: + regex = re.compile(topic_pattern, re.IGNORECASE) + cursor = source_db[source_tables['topics_table']].find( + {"topic_name": regex}).sort("_id", pymongo.ASCENDING) + + topics = list(cursor) + max = len(topics) + print("Total number of topics with the pattern{}: {}".format( + topic_pattern, max)) + + for i in range(0, max): + print("Processing topic: {} {}".format(topics[i]['_id'], + topics[i]['topic_name'])) + pool.spawn(rollup_data, local_source_params, + local_source_params, + datetime.strptime(start_date, '%d%b%YT%H:%M:%S.%f'), + datetime.strptime(end_date, '%d%b%YT%H:%M:%S.%f'), + topics[i]['_id'], topics[i]['topic_name']) + + pool.join() + + except Exception as e: + print("Exception processing data: {}".format(e.args)) + finally: + pool.kill() + print ("Total time for roll up of data : {}".format( + datetime.utcnow() - start)) + if log: + log.close() + + + diff --git a/services/unsupported/MongodbHistorian/scripts/rollup_data_using_groupby.py b/services/unsupported/MongodbHistorian/scripts/rollup_data_using_groupby.py new file mode 100644 index 0000000000..b67b1f9c55 --- /dev/null +++ b/services/unsupported/MongodbHistorian/scripts/rollup_data_using_groupby.py @@ -0,0 +1,151 @@ +# Script used just for reference. This shows how you can group data in hourly +# table into daily or monthly using mongo's aggregate framework. +# This is faster than the logic used in rollup_data_by_time.py but doesn't +# allow any good way to recover from failure + +try: + import pymongo +except: + raise Exception("Required: pymongo") +from datetime import datetime +from numbers import Number + +from bson.objectid import ObjectId + +local_source_params = { + "host": "localhost", + "port": 27017, + "database": "performance_test", + "user": "test", + "passwd": "test", + "authSource":"mongo_test" + } + +local_dest_params = { + "host": "localhost", + "port": 27017, + "database": "performance_test", + "user": "test", + "passwd": "test", + "authSource":"mongo_test" + } + + +def connect_mongodb(connection_params): + print ("setup mongodb") + mongo_conn_str = 'mongodb://{user}:{passwd}@{host}:{port}/{database}' + if connection_params.get('authSource'): + mongo_conn_str = mongo_conn_str+ '?authSource={authSource}' + params = connection_params + mongo_conn_str = mongo_conn_str.format(**params) + print (mongo_conn_str) + mongo_client = pymongo.MongoClient(mongo_conn_str) + db = mongo_client[connection_params['database']] + return db + +def get_table_names(config): + default_table_def = {"table_prefix": "", + "data_table": "data", + "topics_table": "topics", + "meta_table": "meta"} + tables_def = config.get('tables_def', None) + if not tables_def: + tables_def = default_table_def + table_names = dict(tables_def) + table_names["agg_topics_table"] = \ + "aggregate_" + tables_def["topics_table"] + table_names["agg_meta_table"] = \ + "aggregate_" + tables_def["meta_table"] + + table_prefix = tables_def.get('table_prefix', None) + table_prefix = table_prefix + "_" if table_prefix else "" + if table_prefix: + for key, value in table_names.items(): + table_names[key] = table_prefix + table_names[key] + + return table_names + +def daily_rollup(db_params, start_date, end_date): + + dest_db = None + try: + dest_db = connect_mongodb(db_params) + + dest_db['daily_data2'].create_index( + [('topic_id', pymongo.DESCENDING), ('ts', pymongo.DESCENDING)], + unique=True, background=False) + + # temporary index so that initial rollup into daily and monthly + # happens faster + dest_db['daily_data2'].create_index([('ts', pymongo.ASCENDING)], + background=False) + + pipeline = [] + pipeline.append( + {"$match": { + 'ts': {"$gte": start_date, "$lt": end_date}}}) + pipeline.append({"$sort":{'ts':1}}) + pipeline.append({'$group' : { + '_id' : {'topic_id':"$topic_id", 'year':{"$year":"$ts"}, + 'month':{"$month":"$ts"}, + 'dayOfMonth':{"$dayOfMonth":"$ts"}}, + 'year':{"$first":{"$year":"$ts"}}, + 'month':{"$first":{"$month":"$ts"}}, + 'dayOfMonth':{'$first':{"$dayOfMonth":"$ts"}}, + 'ts': {"$first":"$ts"}, + 'topic_id': {"$first":"$topic_id"}, + 'sum': { "$sum": "$sum" }, + 'count': { "$sum": "$count" }, + 'data': {"$push": "$data"} + }}) + pipeline.append({"$unwind":"$data"}) + pipeline.append({"$unwind": "$data"}) + pipeline.append({'$group' : { + '_id': "$_id", + 'ts': {"$first":"$ts"}, + 'year': {"$first": "$year"}, + 'month': {"$first": "$month"}, + 'dayOfMonth': {"$first": "$dayOfMonth"}, + 'h': {"$first":{"$hour":"$ts"}}, + 'm': {"$first":{"$minute":"$ts"}}, + 's': {"$first":{"$second":"$ts"}}, + 'ml': {"$first":{"$millisecond":"$ts"}}, + 'topic_id': {"$first": "$topic_id"}, + 'sum': { "$first": "$sum" }, + 'count': { '$first': "$count" }, + 'data': {'$push': "$data"} + }}) + pipeline.append({'$project':{ + '_id': 0, + 'ts':{ "$subtract" : [ "$ts", + {"$add" : [ + "$ml", + {"$multiply" : [ "$s", 1000 ] }, + {"$multiply" : [ "$m", 60, 1000 ]}, + {"$multiply" : [ "$h", 60, 60, 1000]} + ]} + ]}, + 'topic_id':1, + 'sum': 1, + 'count': 1, + 'data': 1}}) + pipeline.append({"$out":"daily_data2"}) + + dest_db['hourly_data2'].aggregate(pipeline, allowDiskUse=True) + + finally: + if dest_db: + dest_db.client.close() + + +def rollup_data(source, dest, start, end): + daily_rollup(local_dest_params, start, end) + + +if __name__ == '__main__': + start = datetime.utcnow() + rollup_data(local_source_params, local_dest_params, + datetime.strptime('02May2016','%d%b%Y'), + datetime.strptime('03May2016','%d%b%Y')) + print("Total time for roll up of data: {}".format(datetime.utcnow() - start)) + diff --git a/services/unsupported/MongodbHistorian/setup.py b/services/unsupported/MongodbHistorian/setup.py new file mode 100644 index 0000000000..203cd3945b --- /dev/null +++ b/services/unsupported/MongodbHistorian/setup.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from os import path +from setuptools import setup, find_packages + +MAIN_MODULE = 'historian' + +# Find the agent package that contains the main module +packages = find_packages('.') +agent_package = '' +for package in find_packages(): + # Because there could be other packages such as tests + if path.isfile(package + '/' + MAIN_MODULE + '.py') is True: + agent_package = package +if not agent_package: + raise RuntimeError('None of the packages under {dir} contain the file ' + '{main_module}'.format(main_module=MAIN_MODULE + '.py', + dir=path.abspath('.'))) + +# Find the version number from the main module +agent_module = agent_package + '.' + MAIN_MODULE +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) +__version__ = _temp.__version__ + +# Setup +setup( + name=agent_package + 'agent', + version=__version__, + install_requires=['volttron'], + packages=packages, + entry_points={ + 'setuptools.installation': [ + 'eggsecutable = ' + agent_module + ':main', + ] + } +) diff --git a/services/unsupported/MongodbHistorian/tests/fixtures.py b/services/unsupported/MongodbHistorian/tests/fixtures.py new file mode 100644 index 0000000000..2a73a165d3 --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/fixtures.py @@ -0,0 +1,39 @@ +# Module level variables +BASE_DEVICE_TOPIC = "devices/Building/LAB/Device" +BASE_ANALYSIS_TOPIC = "analysis/Economizer/Building/LAB/Device" +ALL_TOPIC = "{}/all".format(BASE_DEVICE_TOPIC) + +mongo_platform = { + "agentid": "mongodb-historian", + "connection": { + "type": "mongodb", + "params": { + "host": "localhost", + "port": 27017, + "database": "mongo_test", + "user": "historian", + "passwd": "historian", + "authSource": "test" + } + } +} + + +def mongo_connection_string(): + mongo_conn_str = 'mongodb://{user}:{passwd}@{host}:{port}/{database}' + + params = mongo_connection_params() + if params.get('authSource'): + mongo_conn_str = mongo_conn_str + '?authSource={authSource}' + mongo_conn_str = mongo_conn_str.format(**params) + return mongo_conn_str + + +def mongo_agent_config(): + return mongo_platform + + +def mongo_connection_params(): + global mongo_platform + mongo_params = mongo_platform['connection']['params'] + return mongo_params diff --git a/services/unsupported/MongodbHistorian/tests/mongod.conf b/services/unsupported/MongodbHistorian/tests/mongod.conf new file mode 100644 index 0000000000..399d2c79b6 --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/mongod.conf @@ -0,0 +1,43 @@ +# mongod.conf + +# for documentation of all options, see: +# http://docs.mongodb.org/manual/reference/configuration-options/ + +# Where and how to store data. +storage: + dbPath: /var/lib/mongodb + journal: + enabled: false + engine: mmapv1 + mmapv1: + smallFiles: true + +# wiredTiger: + +# where to write logging data. +systemLog: + destination: file + logAppend: true + path: /var/log/mongodb/mongod.log + +# network interfaces +net: + port: 27017 + bindIp: 127.0.0.1 + + +#processManagement: + +#security: + +#operationProfiling: + +#replication: + +#sharding: + +## Enterprise-Only Options: + +#auditLog: + +#snmp: diff --git a/services/unsupported/MongodbHistorian/tests/mongosetup.sh b/services/unsupported/MongodbHistorian/tests/mongosetup.sh new file mode 100755 index 0000000000..5e314c46e2 --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/mongosetup.sh @@ -0,0 +1,27 @@ +#!/bin/bash -e + +# Script based upon mongodb installation at +# https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/ + +sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 + +echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list + +sudo apt-get update + +sudo apt-get install -y -f mongodb-org mongodb-org-shell \ + mongodb-org-server mongodb-org-mongos mongodb-org-tools + +echo "mongodb-org hold" | sudo dpkg --set-selections +echo "mongodb-org-server hold" | sudo dpkg --set-selections +echo "mongodb-org-shell hold" | sudo dpkg --set-selections +echo "mongodb-org-mongos hold" | sudo dpkg --set-selections +echo "mongodb-org-tools hold" | sudo dpkg --set-selections + +sudo cp ./services/core/MongodbHistorian/tests/mongod.conf /etc/mongod.conf +sudo chown root.root /etc/mongod.conf + +sudo service mongod restart +# Create users for the database. +mongo admin --eval 'db.createUser( {user: "mongodbadmin", pwd: "V3admin", roles: [ { role: "userAdminAnyDatabase", db: "admin" }]});' +mongo mongo_test -u mongodbadmin -p V3admin --authenticationDatabase admin --eval 'db.createUser( {user: "test", pwd: "test", roles: [ { role: "readWrite", db: "mongo_test" }]});' diff --git a/services/unsupported/MongodbHistorian/tests/purgemongo.sh b/services/unsupported/MongodbHistorian/tests/purgemongo.sh new file mode 100755 index 0000000000..eebe596502 --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/purgemongo.sh @@ -0,0 +1,3 @@ +sudo apt-get purge mongodb-org* +sudo rm -rf /var/lib/mongodb +sudo rm -rf /var/log/mongodb diff --git a/services/unsupported/MongodbHistorian/tests/test_mongohistorian.py b/services/unsupported/MongodbHistorian/tests/test_mongohistorian.py new file mode 100644 index 0000000000..b82da3ee33 --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/test_mongohistorian.py @@ -0,0 +1,1506 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import os +import json +import random +import gevent +import pytest +from dateutil.tz import tzutc +from datetime import datetime +from datetime import timedelta + +from volttron.platform import get_services_core +from volttron.platform.agent import utils +from volttron.platform.agent.utils import get_aware_utc_now, format_timestamp +from volttron.platform.messaging import headers as headers_mod + +try: + import pymongo + + HAS_PYMONGO = True +except: + HAS_PYMONGO = False + +from services.core.MongodbHistorian.tests.fixtures import (ALL_TOPIC, BASE_ANALYSIS_TOPIC, BASE_DEVICE_TOPIC, + mongo_connection_params, mongo_agent_config, + mongo_connection_string) + +query_points = {"oat_point": "Building/LAB/Device/OutsideAirTemperature", + "mixed_point": "Building/LAB/Device/MixedAirTemperature", + "damper_point": "Building/LAB/Device/DamperSignal"} + + +def clean_db(client): + db = client[mongo_connection_params()['database']] + db['data'].drop() + db['topics'].drop() + + +# Create a mark for use within params of a fixture. +pymongo_mark = pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo client available.') + +CLEANUP_CLIENT = True + + +@pytest.fixture(scope="function", params=[pymongo_mark(mongo_agent_config)]) +def database_client(request): + print('connecting to mongo database') + client = pymongo.MongoClient(mongo_connection_string()) + + def close_client(): + if CLEANUP_CLIENT: + print('cleansing mongodb') + clean_db(client) + + if client is not None: + client.close() + + request.addfinalizer(close_client) + return client + + +def install_historian_agent(volttron_instance, config_file): + agent_uuid = volttron_instance.install_agent(agent_dir=get_services_core("MongodbHistorian"), + config_file=config_file, start=True, vip_identity="platform.historian") + return agent_uuid + + +# # Fixtures for setup and teardown +# @pytest.fixture(scope="module", +# params=[ +# pymongo_mark(mongo_platform) +# ]) +# def mongohistorian(request, volttron_instance): +# +# print("** Setting up test_mongohistorian module **") +# # Make database connection +# print("request param", request.param) +# +# # 1: Install historian agent +# # Install and start mongohistorian agent +# agent_uuid = install_historian_agent(volttron_instance, request.param) +# print("agent id: ", agent_uuid) +# +# # 4: add a tear down method to stop mongohistorian agent and the fake +# agent that published to message bus +# def stop_agent(): +# print("In teardown method of module") +# if db_connection: +# db_connection.close() +# print("closed connection to db") +# +# volttron_instance.stop_agent(agent_uuid) +# #volttron_instance.remove_agent(agent_uuid) +# +# publisher.core.stop() +# +# request.addfinalizer(stop_agent) +# return request.param + +def database_name(request): + return request.params['connection']['params']['database'] + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_can_connect(database_client): + """ + Tests whether we can connect to the mongo database at all. Test that we can read/write data on the database while we + are at it. This test assumes that the same information that is used in the mongodbhistorian will be able to used in + this test. + """ + db = database_client[mongo_connection_params()['database']] + result = db.test.insert_one({'x': 1}) + assert result.inserted_id + result = db.test.insert_one({'here': 'Data to search on'}) + assert result.inserted_id + + result = db.test.find_one({'x': 1}) + assert result['x'] == 1 + result = db.test.find_one({'here': 'Data to search on'}) + assert result['here'] == 'Data to search on' + assert db.test.remove() + assert db.test.find().count() == 0 + + +# @pytest.mark.historian +# @pytest.mark.mongodb +# def test_agent_publish_and_query(request, volttron_instance, mongo_config, +# database_client): +# ''' Test the normal operation of the MongodbHistorian agent. +# +# This test will publish some data on the message bus and determine if it +# was written and can be queried back. +# ''' +# clean_db(database_client) +# +# # Install the historian agent (after this call the agent should be +# running +# # on the platform). +# agent_uuid = install_historian_agent(volttron_instance, mongo_config) +# assert agent_uuid is not None +# assert volttron_instance.is_agent_running(agent_uuid) +# +# # Create a publisher and publish to the message bus some fake data. Keep +# # track of the published data so that we can query the historian. +# publisher = volttron_instance.build_agent() +# assert publisher is not None +# expected = publish_devices_fake_data(publisher) +# +# # Query the historian +# for qp in query_points.keys(): +# print("POINT {}".format(qp)) +# result = publisher.vip.rpc.call('platform.historian', +# 'query', +# topic=query_points[qp], +# count=20, +# order="LAST_TO_FIRST").get(timeout=100) +# print("RESULT ", result) +# assert expected['datetime'] == result['values'][0][0] +# assert expected[qp] == result['values'][0][1] +# +# publisher.core.stop() +# if agent_uuid is not None: +# volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_two_hours_of_publishing(volttron_instance, database_client): + clean_db(database_client) + # Install the historian agent (after this call the agent should be running on the platform). + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + + assert agent_uuid is not None + assert volttron_instance.is_agent_running(agent_uuid) + + try: + + # Create a publisher and publish to the message bus some fake data. Keep track of the published data so that we + # can query the historian. + publisher = volttron_instance.build_agent() + assert publisher is not None + expected = publish_minute_data_for_two_hours(publisher) + + # The mongo historian now should have published 2 hours worth of data. Based upon the structure that we expect + # the database to be in we should now have 3 topics present in the database and 2 records for each of the + # 3 data items. + db = database_client.get_default_database() + + assert 3 == db.topics.find().count() + + topic_to_id = {} + for row in db.topics.find(): + topic_to_id[row['topic_name']] = row['_id'] + + gevent.sleep(0.5) + for d, v in expected.items(): + print('d, v: ({}, {})'.format(d, v)) + assert db['data'].find({'ts': d}).count() == 3 + + for t, _id in topic_to_id.items(): + value = db['data'].find_one({'ts': d, 'topic_id': _id})['value'] + assert value == v[t] + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +def publish_minute_data_for_two_hours(agent): + now = get_aware_utc_now() + # expectation[datetime]={oat:b,mixed:c,damper:d} + expectation = {} + + for h in range(2): + data_by_time = {} + + for m in range(60): + # Because timestamps in mongo are only concerned with the first three digits after the decimal we do this to + # give some randomness here. + micro = str(random.randint(0, 999999)) + + now = datetime(now.year, now.month, now.day, h, m, random.randint(0, 59), int(micro)) + # Make some random readings. round to 14 digit precision as mongo only store 14 digit precision + oat_reading = round(random.uniform(30, 100), 14) + mixed_reading = round(oat_reading + random.uniform(-5, 5), 14) + damper_reading = round(random.uniform(0, 100), 14) + + # Create a message for all points. + all_message = [{ + 'OutsideAirTemperature': oat_reading, + 'MixedAirTemperature': mixed_reading, + 'DamperSignal': damper_reading}, + { + 'OutsideAirTemperature': {'units': 'F', 'tz': 'UTC', + 'type': 'float'}, + 'MixedAirTemperature': {'units': 'F', 'tz': 'UTC', + 'type': 'float'}, + 'DamperSignal': {'units': '%', 'tz': 'UTC', 'type': 'float'} + }] + + now_iso_string = format_timestamp(now) + data_by_time[now_iso_string] = { + "oat_point": oat_reading, + "mixed_point": mixed_reading, + "damper_point": damper_reading} + + # now = '2015-12-02T00:00:00' + headers = { + headers_mod.DATE: now_iso_string, + headers_mod.TIMESTAMP: now_iso_string + } + + # Publish messages + agent.vip.pubsub.publish('pubsub', ALL_TOPIC, headers, all_message).get(timeout=10) + + expectation[now] = { + query_points['oat_point']: oat_reading, + query_points['mixed_point']: mixed_reading, + query_points['damper_point']: damper_reading + } + gevent.sleep(0.1) + return expectation + + +def publish_fake_data(agent, now=None, value=None): + """ + Publishes an all message to the passed instances of volttron's message bus. + + The format mimics the format used by VOLTTRON drivers. Uses the passed + agent's vip pubsub to publish an all message. + + returns a dictionary of random readings + { + "datetime": isoformatted string, + "oat_reading": number, + "mixed_reading": number, + "damper_reading": number + } + """ + # Make some random readings + if value: + oat_reading = value + mixed_reading = value + damper_reading = value + else: + oat_reading = round(random.uniform(30, 100), 14) + mixed_reading = round(oat_reading + random.uniform(-5, 5), 14) + damper_reading = round(random.uniform(0, 100), 14) + + # Create a message for all points. + all_message = [{'OutsideAirTemperature': oat_reading, + 'MixedAirTemperature': mixed_reading, + 'DamperSignal': damper_reading}, { + 'OutsideAirTemperature': {'units': 'F', 'tz': 'UTC', 'type': 'float'}, + 'MixedAirTemperature': {'units': 'F', 'tz': 'UTC', 'type': 'float'}, + 'DamperSignal': {'units': '%', 'tz': 'UTC', 'type': 'float'}}] + + # Create timestamp (no parameter to isoformat so the result is a T + # separator) The now value is a string after this function is called. + + # now = now.replace(microsecond=random.randint(0,100)) + # now = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second) + # now = now.isoformat() + if not now: + now = datetime.utcnow() + print('NOW IS: ', now) + + # now = '2015-12-02T00:00:00' + headers = { + headers_mod.DATE: format_timestamp(now), + headers_mod.TIMESTAMP: format_timestamp(now) + } + + # Publish messages + agent.vip.pubsub.publish('pubsub', ALL_TOPIC, headers, all_message).get(timeout=10) + + # The keys for these should be the exact same that are in the query_points dictionary. + return {"datetime": now, "oat_point": oat_reading, "mixed_point": mixed_reading, "damper_point": damper_reading} + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_insert_duplicate(volttron_instance, database_client): + clean_db(database_client) + data_collection = database_client.get_default_database()['data'] + index_model = pymongo.IndexModel([("topic_id", pymongo.DESCENDING), ("ts", pymongo.DESCENDING)], unique=True) + # make sure the data collection has the unique constraint. + data_collection.create_indexes([index_model]) + # Install the historian agent (after this call the agent should be running on the platform). + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + assert agent_uuid is not None + assert volttron_instance.is_agent_running(agent_uuid) + + try: + + oat_reading = round(random.uniform(30, 100), 14) + all_message = [{'OutsideAirTemperature': oat_reading}, { + 'OutsideAirTemperature': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + + publisher = volttron_instance.build_agent() + # Create timestamp (no parameter to isoformat so the result is a T + # separator) The now value is a string after this function is called. + now = get_aware_utc_now() + # now = now.replace(microsecond=random.randint(0,100)) + # now = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second) + # now = now.isoformat() + print('NOW IS: ', now) + + headers = { + headers_mod.DATE: format_timestamp(now), + headers_mod.TIMESTAMP: format_timestamp(now) + } + + # Publish messages + publisher.vip.pubsub.publish('pubsub', ALL_TOPIC, headers, all_message).get(timeout=10) + + gevent.sleep(0.5) + + publisher.vip.pubsub.publish('pubsub', ALL_TOPIC, headers, all_message).get(timeout=10) + + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +def publish_data(publisher, topic, message, now=None): + if now is None: + now = datetime.now() + + now = format_timestamp(now) + headers = { + headers_mod.DATE: now, + headers_mod.TIMESTAMP: now + } + + # Publish messages + publisher.vip.pubsub.publish('pubsub', topic, headers, message).get(timeout=10) + + gevent.sleep(0.5) + return now + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_analysis_topic(volttron_instance, database_client): + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + + try: + publisher = volttron_instance.build_agent() + oat_reading = round(random.uniform(30, 100), 14) + message = [{'FluffyWidgets': oat_reading}, { + 'FluffyWidgets': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + + publisheddt = publish_data(publisher, BASE_ANALYSIS_TOPIC, message) + gevent.sleep(0.1) + + lister = volttron_instance.build_agent() + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + assert topic_list is not None + assert len(topic_list) == 1 + assert 'FluffyWidgets' in topic_list[0] + + result = lister.vip.rpc.call('platform.historian', 'query', + topic=BASE_ANALYSIS_TOPIC[9:] + '/FluffyWidgets').get(timeout=5) + assert result is not None + assert len(result['values']) == 1 + assert isinstance(result['values'], list) + mongoizetimestamp = publisheddt[:-3] + '000+00:00' + assert result['values'][0] == [mongoizetimestamp, oat_reading] + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_get_topic_map(volttron_instance, database_client): + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + + try: + oat_reading = round(random.uniform(30, 100), 14) + all_message = [{'OutsideAirTemperature': oat_reading}, { + 'OutsideAirTemperature': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + + publisher = volttron_instance.build_agent() + publish_data(publisher, ALL_TOPIC, all_message) + + db = database_client.get_default_database() + assert db.topics.count() == 1 + + lister = volttron_instance.build_agent() + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + assert topic_list is not None + assert len(topic_list) == 1 + + # Publish data again for the next point. + publish_data(publisher, ALL_TOPIC, all_message) + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + + # Same topic shouldn't add anything else. + assert topic_list is not None + assert len(topic_list) == 1 + assert topic_list[0] == BASE_DEVICE_TOPIC[8:] + '/OutsideAirTemperature' + + mixed_reading = round(random.uniform(30, 100), 14) + all_message = [{'MixedAirTemperature': mixed_reading}, { + 'MixedAirTemperature': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + + publish_data(publisher, ALL_TOPIC, all_message) + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + + assert topic_list is not None + assert len(topic_list) == 2 + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_basic_function(volttron_instance, database_client): + """ + Test basic functionality of sql historian. Inserts three points as part + of all topic and checks + if all three got into the database + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + + try: + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Publish data to message bus that should be recorded in the mongo database. + publish_fake_data(publish_agent) + expected = publish_fake_data(publish_agent) + gevent.sleep(0.5) + + # Query the historian + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], + count=20, + order="LAST_TO_FIRST").get(timeout=100) + assert expected['datetime'].isoformat()[:-3] + '000+00:00' == result['values'][0][0] + assert result['values'][0][1] == expected['oat_point'] + + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['mixed_point'], + count=20, + order="LAST_TO_FIRST").get(timeout=100) + + assert expected['datetime'].isoformat()[:-3] + '000+00:00' == result['values'][0][0] + assert result['values'][0][1] == expected['mixed_point'] + + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['damper_point'], + count=20, + order="LAST_TO_FIRST").get(timeout=100) + + assert expected['datetime'].isoformat()[:-3] + '000+00:00' == result['values'][0][0] + assert result['values'][0][1] == expected['damper_point'] + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_topic_name_case_change(volttron_instance, database_client): + """ + When case of a topic name changes check if they are saved as two topics + Expected result: query result should be cases insensitive + """ + clean_db(database_client) + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + try: + publisher = volttron_instance.build_agent() + oat_reading = round(random.uniform(30, 100), 14) + message = [{'FluffyWidgets': oat_reading}, { + 'FluffyWidgets': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + + publisheddt = publish_data(publisher, BASE_ANALYSIS_TOPIC, message) + gevent.sleep(0.1) + + lister = volttron_instance.build_agent() + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + assert topic_list is not None + assert len(topic_list) == 1 + assert 'FluffyWidgets' in topic_list[0] + + result = lister.vip.rpc.call('platform.historian', 'query', + topic=BASE_ANALYSIS_TOPIC[9:] + '/FluffyWidgets').get(timeout=5) + assert result is not None + assert len(result['values']) == 1 + assert isinstance(result['values'], list) + mongoizetimestamp = publisheddt[:-3] + '000+00:00' + + assert result['values'][0] == [mongoizetimestamp, oat_reading] + + message = [{'Fluffywidgets': oat_reading}, { + 'Fluffywidgets': {'units': 'F', 'tz': 'UTC', 'type': 'float'}}] + publisheddt = publish_data(publisher, BASE_ANALYSIS_TOPIC, message) + gevent.sleep(0.1) + topic_list = lister.vip.rpc.call('platform.historian', 'get_topic_list').get(timeout=5) + assert topic_list is not None + assert len(topic_list) == 1 + assert 'Fluffywidgets' in topic_list[0] + + result = lister.vip.rpc.call( + 'platform.historian', 'query', + topic=BASE_ANALYSIS_TOPIC[9:] + '/Fluffywidgets', + order="LAST_TO_FIRST").get(timeout=5) + assert result is not None + assert len(result['values']) == 2 + assert isinstance(result['values'], list) + mongoizetimestamp = publisheddt[:-3] + '000+00:00' + assert result['values'][0] == [mongoizetimestamp, oat_reading] + + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_empty_result(volttron_instance, database_client): + """ + When case of a topic name changes check if they are saved as two topics + Expected result: query result should be cases insensitive + """ + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + try: + + lister = volttron_instance.build_agent() + + result = lister.vip.rpc.call('platform.historian', 'query', + topic=BASE_ANALYSIS_TOPIC[9:] + '/FluffyWidgets').get(timeout=5) + print("query result:", result) + assert result == {} + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_multi_topic(volttron_instance, database_client): + """ + Test basic functionality of sql historian. Inserts three points as part + of all topic and checks + if all three got into the database + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + + agent_uuid = install_historian_agent(volttron_instance, mongo_agent_config()) + + try: + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Publish data to message bus that should be recorded in the mongo database. + expected_result = {} + values_dict = {query_points['oat_point']: [], query_points['mixed_point']: []} + for x in range(0, 5): + expected = publish_fake_data(publish_agent) + gevent.sleep(0.5) + if x < 3: + values_dict[query_points['oat_point']].append( + [expected["datetime"].isoformat()[:-3] + '000+00:00', expected["oat_point"]]) + values_dict[query_points['mixed_point']].append( + [expected["datetime"].isoformat()[:-3] + '000+00:00', expected["mixed_point"]]) + expected_result["values"] = values_dict + expected_result["metadata"] = {} + + # Query the historian + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=[query_points['mixed_point'], query_points['oat_point']], + count=3, order="FIRST_TO_LAST").get(timeout=100) + + assert result["metadata"] == expected_result["metadata"] + assert result["values"][query_points['mixed_point']] == expected_result["values"][query_points['mixed_point']] + assert result["values"][query_points['oat_point']] == expected_result["values"][query_points['oat_point']] + finally: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_data_rollup_insert(volttron_instance, database_client): + """ + Test the creation of rolled up data in hourly, daily and monthly data tables when data is published for new or + existing topics + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + print("\n** test_data_rollup_insert **") + + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + db['monthly_data'].drop() + gevent.sleep(0.5) + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 0 + config['periodic_rollup_frequency'] = 2 + agent_uuid = install_historian_agent(volttron_instance, config) + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + # ################### + # Initialization test + # ################### + # Publish data to message bus that should be recorded in the mongo database. All topics are new + now = datetime(year=2016, month=3, day=1, hour=1, minute=1, second=1, microsecond=123, tzinfo=tzutc()) + expected1 = publish_fake_data(publish_agent, now) + expected2 = publish_fake_data(publish_agent, now + timedelta(minutes=1)) + + # publish again. this time topic is not new. rolled up data should + # get append in the array initialized during last publish + expected3 = publish_fake_data(publish_agent, now + timedelta(minutes=4)) + gevent.sleep(0.5) + result = publish_agent.vip.rpc.call('platform.historian', 'query', topic=query_points['oat_point'], count=20, + order="FIRST_TO_LAST").get(timeout=10) + print(result) + gevent.sleep(6) # allow for periodic rollup function to catchup + compare_query_results(db, expected1, expected2, expected3, 'oat_point', result) + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_rollup_query_with_topic_pattern(volttron_instance, database_client): + """ + Test the query of rolled up data from hourly, daily and monthly data + tables + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + publish_t3 = publish_t2 + timedelta(minutes=3) + query_end = publish_t3 + timedelta(seconds=2) + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start = query_end - timedelta(hours=4) + query_start_day = query_end - timedelta(days=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 0 + config['periodic_rollup_frequency'] = 2 + config['rollup_query_start'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['rollup_topic_pattern'] = ".*/OutsideAirTemperature|.*/MixedAirTemperature" + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + expected3 = publish_fake_data(publish_agent, publish_t3) + gevent.sleep(6) + + # test query from data table for damper_point - point not in rollup_topic_pattern configured + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['damper_point'], count=20, + start=query_start.isoformat(), end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print(result) + compare_query_results(db, expected1, expected2, expected3, 'damper_point', result) + + # test query from hourly_data table + # db['data'].drop() + # result = publish_agent.vip.rpc.call( + # 'platform.historian', + # 'query', + # topic=query_points['oat_point'], + # count=20, + # start = query_start.isoformat(), + # end = query_end.isoformat(), + # order="FIRST_TO_LAST").get(timeout=10) + # print(result) + # compare_query_results(db, expected1, expected2, expected3, + # 'oat_point', result) + # verify_hourly_collection(db, expected1, expected2, expected3) + # + # # test damper_point result don't come back from hourly_table. Result + # # should be empty since we dropped damper_point + # result = publish_agent.vip.rpc.call('platform.historian', 'query', + # topic=query_points['damper_point'], count=20, + # start=query_start.isoformat(), end=query_end.isoformat(), + # order="FIRST_TO_LAST").get(timeout=10) + # assert result == {} + # + # # Check query from daily_data + # db['hourly_data'].drop() + # result = publish_agent.vip.rpc.call('platform.historian', 'query', + # topic=query_points['oat_point'], count=20, + # start=query_start_day.isoformat(), + # end= query_end.isoformat(), + # order="FIRST_TO_LAST").get(timeout=10) + # print(result) + # + # compare_query_results(db, expected1, expected2, expected3, + # 'oat_point', result) + # verify_daily_collection(db, expected1, expected2, expected3) + # # test damper_point result don't come back from daily_data. Result + # # should be empty since we dropped damper_point + # result = publish_agent.vip.rpc.call( + # 'platform.historian', 'query', + # topic=query_points['damper_point'], + # count=20, + # start=query_start.isoformat(), + # end=query_end.isoformat(), + # order="FIRST_TO_LAST").get(timeout=10) + # assert result == {} + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_rollup_query(volttron_instance, database_client): + """ + Test the query of rolled up data from hourly, daily and monthly data + tables + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + publish_t3 = publish_t2 + timedelta(minutes=3) + query_end = publish_t3 + timedelta(seconds=2) + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start = query_end - timedelta(hours=4) + query_start_day = query_end - timedelta(days=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 0 + config['periodic_rollup_frequency'] = 0.1 + config['rollup_query_start'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + expected3 = publish_fake_data(publish_agent, publish_t3) + gevent.sleep(10) + + # test query from hourly_data table + db['data'].drop() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=20, + start=query_start.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print(result) + compare_query_results(db, expected1, expected2, expected3, 'oat_point', result) + verify_hourly_collection(db, expected1, expected2, expected3) + + # Check query from daily_data + db['hourly_data'].drop() + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], count=20, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print(result) + + compare_query_results(db, expected3, expected2, expected1, 'oat_point', result) + verify_daily_collection(db, expected3, expected2, expected1) + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_combined_results_from_rollup_and_raw_data(volttron_instance, database_client): + """ + Test querying data with start date earlier than available rollup data and query end date greater than available + rollup data. Historian should query available data from rolled up collection and get the rest from raw data + collection + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + publish_t3 = utils.get_aware_utc_now() + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start_day = publish_t1 - timedelta(days=35) + query_end = publish_t3 + timedelta(seconds=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 1 + config['periodic_rollup_frequency'] = 1 + config['rollup_query_start'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + expected3 = publish_fake_data(publish_agent, publish_t3) + gevent.sleep(6) + + # Only publish_t2 should have gone into rollup collection. + # Remove publish_t2 entry from data collection + print("removing {}".format(publish_t2)) + db['data'].remove({'ts': publish_t2}) + db['daily_data'].remove({'ts': publish_t3.replace(hour=0, minute=0, second=0, microsecond=0)}) + + # Check query + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], count=20, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print(result) + compare_query_results(db, expected3, expected2, expected1, 'oat_point', result) + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_combined_results_from_rollup_and_raw_data(volttron_instance, database_client): + """ + Test querying data with start date earlier than available rollup data and query end date greater than available + rollup data. Historian should query available data from rolled up collection and get the rest from raw data + collection + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + publish_t3 = utils.get_aware_utc_now() + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start_day = publish_t1 - timedelta(days=35) + query_end = publish_t3 + timedelta(seconds=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 1 + config['periodic_rollup_frequency'] = 0.1 + config['rollup_query_start'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + expected3 = publish_fake_data(publish_agent, publish_t3) + gevent.sleep(10) + + # Only publish_t2 should have gone into rollup collection. + # Remove publish_t2 entry from data collection + print("removing {}".format(publish_t2)) + db['data'].remove({'ts': publish_t2}) + db['daily_data'].remove({'ts': publish_t3.replace(hour=0, minute=0, second=0, microsecond=0)}) + + # Check query + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], count=20, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print(result) + compare_query_results(db, expected3, expected2, expected1, 'oat_point', result) + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_combined_results_rollup_and_raw_data_with_count(volttron_instance, database_client): + """ + Test querying data with start date earlier than available rollup data and query end date greater than available + rollup data. Historian should query available data from rolled up collection and get the rest from raw data + collection + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + publish_t3 = utils.get_aware_utc_now() - timedelta(minutes=1) + publish_t4 = utils.get_aware_utc_now() + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start_day = publish_t1 - timedelta(days=35) + query_end = publish_t4 + timedelta(seconds=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 1 + config['periodic_rollup_frequency'] = 0.1 + config['rollup_query_start'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = publish_t2.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + expected3 = publish_fake_data(publish_agent, publish_t3) + expected4 = publish_fake_data(publish_agent, publish_t4) + gevent.sleep(10) + + # Only publish_t2 should have gone into rollup collection. + # Remove publish_t2 entry from data collection so that is is only + # available in hourly and daily collections + print("removing {}".format(publish_t2)) + db['data'].remove({'ts': publish_t2}) + db['daily_data'].remove({'ts': publish_t3.replace(hour=0, minute=0, second=0, microsecond=0)}) + + # result from data table alone + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=1, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print("Case 1: {}".format(result)) + compare_query_results(db, expected4, None, None, 'oat_point', result) + + # result from data table alone + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=1, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print("Case 2: {}".format(result)) + compare_query_results(db, expected1, None, None, 'oat_point', result) + + # result from rollup table alone + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=1, + start=publish_t2.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print("Case 3: {}".format(result)) + compare_query_results(db, expected2, None, None, 'oat_point', result) + + # combined result + result = publish_agent.vip.rpc.call( + 'platform.historian', 'query', + topic=query_points['oat_point'], count=2, + start=query_start_day.isoformat(), end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print("Case 4: {}".format(result)) + compare_query_results(db, expected1, expected2, None, 'oat_point', result) + + # combined result + result = publish_agent.vip.rpc.call( + 'platform.historian', 'query', + topic=query_points['oat_point'], count=3, + start=query_start_day.isoformat(), end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print("Case 5: {}".format(result)) + compare_query_results(db, expected4, expected3, expected2, 'oat_point', result) + + # results only from raw data + result = publish_agent.vip.rpc.call( + 'platform.historian', 'query', + topic=query_points['oat_point'], + count=2, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print("Case 6: {}".format(result)) + compare_query_results(db, expected4, expected3, None, 'oat_point', result) + + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_dict_insert_special_character(volttron_instance, database_client): + """ + Test the inserting special characters + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + publish_t2 = publish_t1 + timedelta(minutes=1) + + query_end = publish_t2 + timedelta(seconds=2) + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start = query_end - timedelta(hours=4) + query_start_day = query_end - timedelta(days=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 0 + config['periodic_rollup_frequency'] = 0.2 + config['rollup_query_start'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_dict_insert_special_character **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + dict1 = {"key.1": "value1", "$": 1} + expected1 = publish_fake_data(publish_agent, publish_t1, dict1) + expected2 = publish_fake_data(publish_agent, publish_t2, dict1) + gevent.sleep(15) + + # test query from hourly_data table + db['data'].drop() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=20, + start=query_start.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print(result) + compare_query_results(db, expected1, expected2, None, 'oat_point', result) + + # Check query from daily_data + db['hourly_data'].drop() + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], count=20, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="LAST_TO_FIRST").get(timeout=10) + print(result) + + compare_query_results(db, expected2, expected1, None, 'oat_point', result) + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_insert_multiple_data_per_minute(volttron_instance, database_client): + """ + Test the query of rolled up data from hourly, daily and monthly data tables + :param database_client: + :param volttron_instance: The instance against which the test is run + """ + global query_points + agent_uuid = None + try: + # Clean data and roll up tables + db = database_client.get_default_database() + db['data'].drop() + db['topics'].drop() + db['meta'].drop() + db['hourly_data'].drop() + db['daily_data'].drop() + + publish_t1 = datetime(year=2016, month=3, day=1, hour=1, minute=10, second=1, microsecond=0, tzinfo=tzutc()) + # test insert and query when there is more than 1 record in the same minute + publish_t2 = publish_t1 + timedelta(seconds=5) + query_end = publish_t2 + timedelta(seconds=2) + # query time period should be greater than 3 hours for historian to use + # hourly_data collection and >= 1 day to use daily_data table + query_start = query_end - timedelta(hours=4) + query_start_day = query_end - timedelta(days=2) + + config = mongo_agent_config() + config['periodic_rollup_initial_wait'] = 0.1 + config['rollup_query_end'] = 0 + config['periodic_rollup_frequency'] = 0.1 + config['rollup_query_start'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + config['initial_rollup_start_time'] = query_start_day.strftime('%Y-%m-%dT%H:%M:%S.%f') + + agent_uuid = install_historian_agent(volttron_instance, config) + print("\n** test_data_rollup_insert **") + + publish_agent = volttron_instance.build_agent() + + version = publish_agent.vip.rpc.call('platform.historian', 'get_version').get(timeout=5) + + version_nums = version.split(".") + if int(version_nums[0]) < 2: + pytest.skip("Only version >= 2.0 support rolled up data.") + + expected1 = publish_fake_data(publish_agent, publish_t1) + expected2 = publish_fake_data(publish_agent, publish_t2) + gevent.sleep(10) + + # test query from hourly_data table + db['data'].drop() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=query_points['oat_point'], + count=20, + start=query_start.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print(result) + compare_query_results(db, expected1, expected2, None, 'oat_point', result) + verify_hourly_collection(db, expected1, expected2) + + # Check query from daily_data + db['hourly_data'].drop() + result = publish_agent.vip.rpc.call('platform.historian', 'query', + topic=query_points['oat_point'], count=20, + start=query_start_day.isoformat(), + end=query_end.isoformat(), + order="FIRST_TO_LAST").get(timeout=10) + print(result) + + compare_query_results(db, expected1, expected2, None, 'oat_point', result) + verify_daily_collection(db, expected1, expected2) + finally: + if agent_uuid: + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) + + +def compare_query_results(db, expected1, expected2, expected3, query_point, result): + expected_t1 = format_expected_time(expected1) + assert expected_t1 == result['values'][0][0] + assert result['values'][0][1] == expected1[query_point] + if expected2: + expected_t2 = format_expected_time(expected2) + assert expected_t2 == result['values'][1][0] + assert result['values'][1][1] == expected2[query_point] + if expected3: + expected_t3 = format_expected_time(expected3) + assert expected_t3 == result['values'][2][0] + assert result['values'][2][1] == expected3[query_point] + + +def verify_daily_collection(db, expected1, expected2, expected3=None): + # verify daily data + expected_t1 = format_expected_time(expected1) + expected_t2 = format_expected_time(expected2) + t1_hour_min = expected1['datetime'].replace(second=0, microsecond=0) + t2_hour_min = expected2['datetime'].replace(second=0, microsecond=0) + expected_t3 = None + if expected3: + expected_t3 = format_expected_time(expected3) + cursor = db['topics'].find({'topic_name': query_points['oat_point']}) + rows = list(cursor) + id = rows[0]['_id'] + cursor = db['daily_data'].find({'topic_id': id}) + rows = list(cursor) + assert len(rows[0]['data']) == 24 * 60 + # if it is same day and same minute + if t1_hour_min == t2_hour_min: + rolled_up_data1 = rows[0]['data'][ + expected1['datetime'].hour * 60 + expected1['datetime'].minute][0] + rolled_up_data2 = rows[0]['data'][expected2['datetime'].hour * 60 + expected1['datetime'].minute][1] + else: + rolled_up_data1 = rows[0]['data'][expected1['datetime'].hour * 60 + expected1['datetime'].minute][0] + rolled_up_data2 = rows[0]['data'][expected2['datetime'].hour * 60 + expected2['datetime'].minute][0] + compare_rolled_up_data(rolled_up_data1, expected_t1, expected1['oat_point']) + compare_rolled_up_data(rolled_up_data2, expected_t2, expected2['oat_point']) + if expected3: + rolled_up_data3 = rows[0]['data'][ + expected3['datetime'].hour * 60 + expected3['datetime'].minute][0] + compare_rolled_up_data(rolled_up_data3, expected_t3, expected3['oat_point']) + + +def verify_hourly_collection(db, expected1, expected2, expected3=None): + # verify hourly rollup + expected_t1 = format_expected_time(expected1) + expected_t2 = format_expected_time(expected2) + t1_hour = expected1['datetime'].replace(second=0, microsecond=0) + t2_hour = expected2['datetime'].replace(second=0, microsecond=0) + expected_t3 = None + if expected3: + expected_t3 = format_expected_time(expected3) + cursor = db['topics'].find({'topic_name': query_points['oat_point']}) + rows = list(cursor) + id = rows[0]['_id'] + cursor = db['hourly_data'].find({'topic_id': id}) + rows = list(cursor) + assert len(rows[0]['data']) == 60 + print(rows[0]['data']) + if t1_hour == t2_hour: + rolled_up_data1 = rows[0]['data'][expected1['datetime'].minute][0] + rolled_up_data2 = rows[0]['data'][expected1['datetime'].minute][1] + else: + rolled_up_data1 = rows[0]['data'][expected1['datetime'].minute][0] + rolled_up_data2 = rows[0]['data'][expected2['datetime'].minute][0] + + compare_rolled_up_data(rolled_up_data1, expected_t1, expected1['oat_point']) + compare_rolled_up_data(rolled_up_data2, expected_t2, expected2['oat_point']) + if expected3 and expected_t3: + rolled_up_data3 = rows[0]['data'][expected3['datetime'].minute][0] + compare_rolled_up_data(rolled_up_data3, expected_t3, expected3['oat_point']) + + +def format_expected_time(expected1): + expected_t1 = utils.format_timestamp(expected1['datetime']) + expected_t1 = expected_t1[:-9] + '000+00:00' + return expected_t1 + + +def compare_rolled_up_data(data_from_db_query, expected_time, expected_value): + assert utils.format_timestamp(data_from_db_query[0]) + '+00:00' == expected_time + assert data_from_db_query[1] == expected_value + + +@pytest.mark.historian +@pytest.mark.mongodb +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_manage_db_size(volttron_instance, database_client): + clean_db(database_client) + + # set config parameter to automatically delete data + config = dict(mongo_agent_config()) + config["history_limit_days"] = 6 + + # start up the agent + agent_uuid = install_historian_agent(volttron_instance, config) + + assert agent_uuid is not None + assert volttron_instance.is_agent_running(agent_uuid) + + # put some fake data in the database + db = database_client.get_default_database() + collection_names = ("data", "hourly_data", "daily_data") + doc = {"ts": datetime(1970, 1, 1), "message": "testdata"} + for collection_name in collection_names: + db[collection_name].insert_one(doc) + + for collection_name in collection_names: + assert db[collection_name].find_one({"message": "testdata"}) is not None + + # publish something that the database should see + publisher = volttron_instance.build_agent() + assert publisher is not None + publish_fake_data(publisher) + + gevent.sleep(6) + # make sure that the database deletes old data + for collection_name in collection_names: + assert db[collection_name].find_one({"message": "testdata"}) is None + + # clean up + volttron_instance.stop_agent(agent_uuid) + volttron_instance.remove_agent(agent_uuid) diff --git a/services/unsupported/MongodbHistorian/tests/test_prod_query_mongo.py b/services/unsupported/MongodbHistorian/tests/test_prod_query_mongo.py new file mode 100644 index 0000000000..4a78f0e5eb --- /dev/null +++ b/services/unsupported/MongodbHistorian/tests/test_prod_query_mongo.py @@ -0,0 +1,355 @@ +import pytest +from datetime import datetime +# building name replaced. replace before testing +from volttron.platform import get_services_core +from fixtures import (ALL_TOPIC, BASE_ANALYSIS_TOPIC, BASE_DEVICE_TOPIC, + mongo_connection_params, mongo_agent_config, + mongo_connection_string) + +AHU1_temp = "Economizer_RCx/PNNL/BUILDING1/AHU1/Temperature Sensor Dx/diagnostic message" +AHU2_temp = "Economizer_RCx/PNNL/BUILDING1/AHU2/Temperature Sensor Dx/diagnostic message" +AHU3_temp = "Economizer_RCx/PNNL/BUILDING1/AHU3/Temperature Sensor Dx/diagnostic message" +AHU4_temp = "Economizer_RCx/PNNL/BUILDING1/AHU4/Temperature Sensor Dx/diagnostic message" +AHU2_eco = "Economizer_RCx/PNNL/BUILDING1/AHU2/Not Economizing When Unit Should " \ + "Dx/diagnostic message" +AHU2_outdoor_air = "Economizer_RCx/PNNL/BUILDING1/AHU2/Excess Outdoor-air Intake " \ + "Dx/diagnostic message" +AHU1_outdoor_air = "Economizer_RCx/PNNL/BUILDING1/AHU1/Excess Outdoor-air Intake " \ + "Dx/diagnostic message" + +AHU1_VAV129 = "PNNL/BUILDING1/AHU1/VAV129/ZoneOutdoorAirFlow" +AHU1_VAV127B = "PNNL/BUILDING1/AHU1/VAV127B/ZoneOutdoorAirFlow" +AHU1_VAV119 = "PNNL/BUILDING1/AHU1/VAV119/ZoneOutdoorAirFlow" +AHU1_VAV143 = "PNNL/BUILDING1/AHU1/VAV143/ZoneOutdoorAirFlow" +AHU1_VAV150 = "PNNL/BUILDING1/AHU1/VAV150/ZoneOutdoorAirFlow" +AHU1_VAV152 = "PNNL/BUILDING1/AHU1/VAV152/ZoneOutdoorAirFlow" + +AHU1_VAV127A_temp = "PNNL/BUILDING1/AHU1/VAV127A/ZoneTemperature (East)" +AHU1_VAV127B_temp = "PNNL/BUILDING1/AHU1/VAV127B/ZoneTemperature (East)" + +multi_topic_list2 = \ + ["Economizer_RCx/PNNL/BUILDING1/AHU2/Temperature Sensor Dx/energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Not Economizing When Unit Should Dx/" + "energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Economizing When Unit Should Not Dx/" + "energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Excess Outdoor-air Intake Dx/energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Insufficient Outdoor-air Intake Dx/energy " + "impact", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Economizing When Unit Should Not " + "Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Not Economizing When Unit Should " + "Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Temperature Sensor Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Excess Outdoor-air Intake Dx/diagnostic " + "message", + "Economizer_RCx/PNNL/BUILDING1/AHU2/Insufficient Outdoor-air Intake " + "Dx/diagnostic message"] + +multi_topic_list1 = \ + ["Economizer_RCx/PNNL/BUILDING1/AHU1/Temperature Sensor Dx/energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Not Economizing When Unit Should Dx/" + "energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Economizing When Unit Should Not Dx/" + "energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Excess Outdoor-air Intake Dx/energy impact", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Insufficient Outdoor-air Intake Dx/energy " + "impact", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Economizing When Unit Should Not " + "Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Not Economizing When Unit Should " + "Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Temperature Sensor Dx/diagnostic message", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Excess Outdoor-air Intake Dx/diagnostic " + "message", + "Economizer_RCx/PNNL/BUILDING1/AHU1/Insufficient Outdoor-air Intake " + "Dx/diagnostic message"] + +# ["Economizer_RCx/PNNL/BUILDING1/AHU1/Temperature Sensor Dx/diagnostic message","Economizer_RCx/PNNL/BUILDING1/AHU2/Temperature Sensor Dx/diagnostic message","Economizer_RCx/PNNL/BUILDING1/AHU3/Temperature Sensor Dx/diagnostic message","Economizer_RCx/PNNL/BUILDING1/AHU4/Temperature Sensor Dx/diagnostic message"] +try: + import pymongo + + HAS_PYMONGO = True +except: + HAS_PYMONGO = False + +mongo_platform = { + "connection": { + "type": "mongodb", + "params": { + "host": "localhost", + "port": 27017, + "database": "performance_test", + "user": "test", + "passwd": "test", + "authSource": "mongo_test" + } + } +} + +pytestmark = pytest.mark.skip(reason="Performance test. Not for CI") + + +# Create a mark for use within params of a fixture. +pytestmark = pytest.mark.skip(reason="Performance test. Not for CI") +pymongo_mark = pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo client available.') + + +@pytest.fixture(scope="function", params=[pymongo_mark(mongo_agent_config)]) +def database_client_prod(request): + print('connecting to mongo database') + client = pymongo.MongoClient(mongo_connection_string()) + + def close_client(): + if client is not None: + client.close() + + request.addfinalizer(close_client) + return client + + +@pytest.mark.timeout(180) +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_basic_function_week_data(volttron_instance, database_client_prod): + """ + Test basic functionality of sql historian. Inserts three points as part of all topic and checks + if all three got into the database + :param volttron_instance: The instance against which the test is run + """ + + # print('HOME', volttron_instance.volttron_home) + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Query the historian + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic="Economizer_RCx/PNNL/BUILDING1/AHU1/Temperature Sensor Dx/diagnostic message", + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic="Economizer_RCx/PNNL/BUILDING1/AHU2/Temperature Sensor Dx/diagnostic " + "message", + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic="Economizer_RCx/PNNL/BUILDING1/AHU3/Temperature Sensor Dx/diagnostic " + "message", + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic="Economizer_RCx/PNNL/BUILDING1/AHU4/Temperature Sensor Dx/diagnostic " + "message", + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + +@pytest.mark.timeout(180) +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_basic_function_month_data(volttron_instance, database_client_prod): + """ + Test basic functionality of sql historian. Inserts three points as part of all topic and checks + if all three got into the database + :param volttron_instance: The instance against which the test is run + """ + + # print('HOME', volttron_instance.volttron_home) + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Query the historian + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV129, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV127B, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV127A_temp, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV127B_temp, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV119, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV143, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=AHU1_VAV150, + start='2016-04-01 00:00:00.000000Z', + end='2016-05-01 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + +@pytest.mark.timeout(180) +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_basic_function_week_multi_topic(volttron_instance, database_client_prod): + """ + Test basic functionality of sql historian. Inserts three points as part of all topic and checks + if all three got into the database + :param volttron_instance: The instance against which the test is run + """ + + # print('HOME', volttron_instance.volttron_home) + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Query the historian + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=multi_topic_list1, + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=multi_topic_list2, + start='2016-04-01 00:00:00.000000Z', + end='2016-04-08 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + +@pytest.mark.timeout(180) +@pytest.mark.skipif(not HAS_PYMONGO, reason='No pymongo driver') +def test_basic_function_2week_multi_topic(volttron_instance, database_client_prod): + """ + Test basic functionality of sql historian. Inserts three points as part of all topic and checks + if all three got into the database + :param volttron_instance: The instance against which the test is run + """ + + # print('HOME', volttron_instance.volttron_home) + print("\n** test_basic_function **") + + publish_agent = volttron_instance.build_agent() + + # Query the historian + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=multi_topic_list1, + start='2016-04-01 00:00:00.000000Z', + end='2016-04-15 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) + + before = datetime.now() + result = publish_agent.vip.rpc.call( + 'platform.historian', + 'query', + topic=multi_topic_list2, + start='2016-04-01 00:00:00.000000Z', + end='2016-04-15 00:00:00.000000Z', + count=35000 + ).get(timeout=100) + print("Time taken{}".format(datetime.now() - before)) + print("result count {}".format(len(result['values']))) diff --git a/services/unsupported/OpenADRVenAgent/IDENTITY b/services/unsupported/OpenADRVenAgent/IDENTITY new file mode 100644 index 0000000000..682dca497b --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/IDENTITY @@ -0,0 +1 @@ +openadrven \ No newline at end of file diff --git a/services/unsupported/OpenADRVenAgent/README.md b/services/unsupported/OpenADRVenAgent/README.md new file mode 100644 index 0000000000..cf876e80a0 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/README.md @@ -0,0 +1,120 @@ +# OpenADRVenAgent + +OpenADR (Automated Demand Response) is a standard for alerting and responding to the need to adjust electric power +consumption in response to fluctuations in grid demand. + +For further information about OpenADR and this agent, please see the OpenADR documentation in VOLTTRON ReadTheDocs. + +## Dependencies +The VEN agent depends on some third-party libraries that are not in the standard VOLTTRON installation. +They should be installed in the VOLTTRON virtual environment prior to building the agent. Use requirements.txt in the +agent directory to install the requirements + +``` +cd $VOLTTRON_ROOT/services/core/OpenADRVenAgent +pip install -r requirements.txt +``` + +## Configuration Parameters + +The VEN agent’s configuration file contains JSON that includes several parameters for configuring VTN server communications and other behavior. A sample configuration file, config, has been provided in the agent directory. + +The VEN agent supports the following configuration parameters: + +|Parameter|Example|Description| +|---------|-------|-----------| +|db\_path|“\$VOLTTRON\_HOME/data/|Pathname of the agent's sqlite database. Shell| +||openadr.sqlite”|variables will be expanded if they are present| +|||in the pathname.| +|ven\_id|“0”|The OpenADR ID of this virtual end node. Identifies| +|||this VEN to the VTN. If automated VEN registration| +|||is used, the ID is assigned by the VTN at that| +|||time. If the VEN is registered manually with the| +|||VTN (i.e., via configuration file settings), then| +|||a common VEN ID should be entered in this config| +|||file and in the VTN's site definition.| +|ven\_name|"ven01"|Name of this virtual end node. This name is used| +|||during automated registration only, identiying| +|||the VEN before its VEN ID is known.| +|vtn\_id|“vtn01”|OpenADR ID of the VTN with which this VEN| +|||communicates.| +|vtn\_address|“.|URL and port number of the VTN.| +||ki-evi.com:8000”|| +|send\_registration|“False”|(“True” or ”False”) If “True”, the VEN sends| +|||a one-time automated registration request to| +|||the VTN to obtain the VEN ID. If automated| +|||registration will be used, the VEN should be run| +|||in this mode initially, then shut down and run| +|||with this parameter set to “False” thereafter.| +|security\_level|“standard”|If 'high', the VTN and VEN use a third-party| +|||signing authority to sign and authenticate each| +|||request. The default setting is “standard”: the| +|||XML payloads do not contain Signature elements.| +|poll\_interval\_secs|30|(integer) How often the VEN should send an OadrPoll| +|||request to the VTN. The poll interval cannot be| +|||more frequent than the VEN’s 5-second process| +|||loop frequency.| +|log\_xml|“False”|(“True” or “False”) Whether to write each| +|||inbound/outbound request’s XML data to the| +|||agent's log.| +|opt\_in\_timeout\_secs|1800|(integer) How long to wait before making a| +|||default optIn/optOut decision.| +|opt\_in\_default\_decision|“optOut”|(“True” or “False”) Which optIn/optOut choice| +|||to make by default.| +|request\_events\_on\_startup|"False"|("True" or "False") Whether to ask the VTN for a| +|||list of current events during VEN startup.| +|report\_parameters|(see below)|A dictionary of definitions of reporting/telemetry| +|||parameters.| + +Reporting Configuration +======================= + +The VEN’s reporting configuration, specified as a dictionary in the agent configuration, defines each telemetry element (metric) that the VEN can report to the VTN, if requested. By default, it defines reports named “telemetry” and "telemetry\_status", with a report configuration dictionary containing the following parameters: + +|"telemetry" report: parameters|Example|Description| +|------------------------------|-------|-----------| +|report\_name|"TELEMETRY\_USAGE"|Friendly name of the report.| +|report\_name\_metadata|"METADATA\_TELEMETRY\_USAGE"|Friendly name of the report’s metadata, when sent| +|||by the VEN’s oadrRegisterReport request.| +|report\_specifier\_id|"telemetry"|Uniquely identifies the report’s data set.| +|report\_interval\_secs\_default|"300"|How often to send a reporting update to the VTN.| +|telemetry\_parameters (baseline\_power\_kw): r\_id|"baseline\_power"|(baseline\_power) Unique ID of the metric.| +|telemetry\_parameters (baseline\_power\_kw): report\_type|"baseline"|(baseline\_power) The type of metric being reported.| +|telemetry\_parameters (baseline\_power\_kw): reading\_type|"Direct Read"|(baseline\_power) How the metric was calculated.| +|telemetry\_parameters (baseline\_power\_kw): units|"powerReal"|(baseline\_power) The reading's data type.| +|telemetry\_parameters (baseline\_power\_kw): method\_name|"get\_baseline\_power"|(baseline\_power) The VEN method to use when| +|||extracting the data for reporting.| +|telemetry\_parameters (baseline\_power\_kw): min\_frequency|30|(baseline\_power) The metric’s minimum sampling| +|||frequency.| +|telemetry\_parameters (baseline\_power\_kw): max\_frequency|60|(baseline\_power) The metric’s maximum sampling| +|||frequency.| +|telemetry\_parameters (current\_power\_kw): r\_id|"actual\_power"|(current\_power) Unique ID of the metric.| +|telemetry\_parameters (current\_power\_kw): report\_type|"reading"|(current\_power) The type of metric being reported.| +|telemetry\_parameters (current\_power\_kw): reading\_type|"Direct Read"|(current\_power) How the metric was calculated.| +|telemetry\_parameters (current\_power\_kw): units|"powerReal"|(baseline\_power) The reading's data type.| +|telemetry\_parameters (current\_power\_kw): method\_name|"get\_current\_power"|(current\_power) The VEN method to use when| +|||extracting the data for reporting.| +|telemetry\_parameters (current\_power\_kw): min\_frequency|30|(current\_power) The metric’s minimum sampling| +|||frequency.| +|telemetry\_parameters (current\_power\_kw): max\_frequency|60|(current\_power) The metric’s maximum sampling| +|||frequency.| + +|"telemetry\_status" report: parameters|Example|Description| +|--------------------------------------|-------|-----------| +|report\_name|"TELEMETRY\_STATUS"|Friendly name of the report.| +|report\_name\_metadata|"METADATA\_TELEMETRY\_STATUS"|Friendly name of the report’s metadata, when sent| +|||by the VEN’s oadrRegisterReport request.| +|report\_specifier\_id|"telemetry\_status"|Uniquely identifies the report’s data set.| +|report\_interval\_secs\_default|"300"|How often to send a reporting update to the VTN.| +|telemetry\_parameters (Status): r\_id|"Status"|Unique ID of the metric.| +|telemetry\_parameters (Status): report\_type|"x-resourceStatus"|The type of metric being reported.| +|telemetry\_parameters (Status): reading\_type|"x-notApplicable"|How the metric was calculated.| +|telemetry\_parameters (Status): units|""|The reading's data type.| +|telemetry\_parameters (Status): method\_name|""|The VEN method to use when extracting the data| +|||for reporting.| +|telemetry\_parameters (Status): min\_frequency|60|The metric’s minimum sampling frequency.| +|telemetry\_parameters (Status): max\_frequency|120|The metric’s maximum sampling frequency.| + + + + diff --git a/services/unsupported/OpenADRVenAgent/__init__.py b/services/unsupported/OpenADRVenAgent/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_BOTH0002_Cert.pem b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_BOTH0002_Cert.pem new file mode 100644 index 0000000000..6cbb2e76f8 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_BOTH0002_Cert.pem @@ -0,0 +1,65 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIJAMAk6qaC/BDRMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV +BAYTAlVTMRkwFwYDVQQKExBPcGVuQURSIEFsbGlhbmNlMRgwFgYDVQQLEw9SU0Eg +Um9vdCBDQTAwMDExKjAoBgNVBAMTIVRFU1QgT3BlbkFEUiBBbGxpYW5jZSBSU0Eg +Um9vdCBDQTAeFw0xNDAyMTkxODM0MjhaFw00NDAyMTkxODM0MjhaMGwxCzAJBgNV +BAYTAlVTMRkwFwYDVQQKExBPcGVuQURSIEFsbGlhbmNlMRcwFQYDVQQLEw5SU0Eg +VkVOIENBMDAwMTEpMCcGA1UEAxMgVEVTVCBPcGVuQURSIEFsbGlhbmNlIFJTQSBW +RU4gQ0EwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDDIuq70nfxiKTr +4q+nc8lhvsvgBcBv/sri7YCk36+ztFpCsA6QA7g5y/tU/omKTP5c9qD1+b4KP0ZR +Gnd/I5jSfgNDKLE3BpOk1jamk4qyyUWKnBgQKKgHDoM84pYMSEO5JpRJL6T1lMml +Y7SYit53+TWHm4tHzZnRWCQrpPnc8tZvy4JS9MG9j3PvvxDT6TFLkuy7XGtcPKNI +zhABSOeiOFmen1fEHMzUUfMs2aljPutUVZzlXHm+hv02Y1nfU+EE5SGz+oHDFNOm +rwLivOjAhCw2c4NanTPKBEv1zhOY9yMpYQGHX/a4S7z3MXVwJe8xVLBo5ycx1BYX ++7w9MuUZ1cYkJVBeaeJ9AgvVwD2TFk6o4GYdQXqG/gVKO/aWaeZNcLiFxH+8Em8o +pLFy3sTwph2PkWKMxYIJBt/TByY8DgaiTUzfA6TcFgNmhLGuyisW19DFgMm3XfM3 +fqSw2G4WBdIWBbCheDf4f4OxjvSxAokpa0BFhbmGF9su8ZFDPNUCAwEAAaOBpTCB +ojAdBgNVHQ4EFgQUhZV8Mgit2zkFxKk01XoYMehk76owDgYDVR0PAQH/BAQDAgEG +MCMGA1UdEQQcMBqkGDAWMRQwEgYDVQQDEwtTWU1DLTMwNzItMTASBgNVHRMBAf8E +CDAGAQH/AgEAMBcGA1UdIAQQMA4wDAYKKwYBBAGCxC8BATAfBgNVHSMEGDAWgBTM +cmzJEF7WYdxcxCU+wQ6axmPXMjANBgkqhkiG9w0BAQsFAAOCAgEAn/K4jfp7M5Xl +SCYS9htW4EdjCN7v84oCGVc3DFTHalz/wkFVyxhVl1TEw8lWGYG/D97D+fCgPjCa +3PXgOT646tkMqwPQSzUWuTGDYqfsusUqVBl3nXZZqWsRwylaavJAHGK0RvulpVq7 +XeQigrGT1Y0BiRAqGyPq3ai05tMrFSZAGqDVzkJct1zNc+I2eOUEzwJzDgPvTq2o +DN9A6CsHQcjUBGZC086YQmPLbevNo2q6pdxvmSNDT/c6bffPlVRXbweC1iVLWtvP +SPkgDbDqMnGwLFyZcCaqQtRnV1ZCFUjwcPr4zAmXNpYZjIWjh40/giQ7n49hcgtg +Cfjj2uxsq7bBTv+VKRFoytbEP83/Mn0jURaQeNacBly2upqtp6Nt9B2R8UuSB8jH +Jp+NEjaZc+6A1mIkPHkGV9uoLLiXc4wBVP6SFyLmqqCGgAr2VS04KKL5VEXywjBI +goRF2jseS2pJPihaar+LG87ielNFm6h6zl8DTdhMBE7N+Fj4jWU2oUygYs9PLrg3 +f0M2ST9uVybAjPJ8exBIXrN4VJoR6ve6m+mxVkrAgSUa+LUMiqHyixjXmyGlXQIl +72DNqvY8HTTE+P0l6FVC7glcVTyygUv5vVE6pwmm1rbk2upKshQnUelXydYJBsAH +pOR5GJfzMPXZ7V4NjhHPVqJWJQdTsNA= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_MCA0002_Cert.pem b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_MCA0002_Cert.pem new file mode 100644 index 0000000000..6babf4e273 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_MCA0002_Cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIJAMAk6qaC/BDRMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV +BAYTAlVTMRkwFwYDVQQKExBPcGVuQURSIEFsbGlhbmNlMRgwFgYDVQQLEw9SU0Eg +Um9vdCBDQTAwMDExKjAoBgNVBAMTIVRFU1QgT3BlbkFEUiBBbGxpYW5jZSBSU0Eg +Um9vdCBDQTAeFw0xNDAyMTkxODM0MjhaFw00NDAyMTkxODM0MjhaMGwxCzAJBgNV +BAYTAlVTMRkwFwYDVQQKExBPcGVuQURSIEFsbGlhbmNlMRcwFQYDVQQLEw5SU0Eg +VkVOIENBMDAwMTEpMCcGA1UEAxMgVEVTVCBPcGVuQURSIEFsbGlhbmNlIFJTQSBW +RU4gQ0EwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDDIuq70nfxiKTr +4q+nc8lhvsvgBcBv/sri7YCk36+ztFpCsA6QA7g5y/tU/omKTP5c9qD1+b4KP0ZR +Gnd/I5jSfgNDKLE3BpOk1jamk4qyyUWKnBgQKKgHDoM84pYMSEO5JpRJL6T1lMml +Y7SYit53+TWHm4tHzZnRWCQrpPnc8tZvy4JS9MG9j3PvvxDT6TFLkuy7XGtcPKNI +zhABSOeiOFmen1fEHMzUUfMs2aljPutUVZzlXHm+hv02Y1nfU+EE5SGz+oHDFNOm +rwLivOjAhCw2c4NanTPKBEv1zhOY9yMpYQGHX/a4S7z3MXVwJe8xVLBo5ycx1BYX ++7w9MuUZ1cYkJVBeaeJ9AgvVwD2TFk6o4GYdQXqG/gVKO/aWaeZNcLiFxH+8Em8o +pLFy3sTwph2PkWKMxYIJBt/TByY8DgaiTUzfA6TcFgNmhLGuyisW19DFgMm3XfM3 +fqSw2G4WBdIWBbCheDf4f4OxjvSxAokpa0BFhbmGF9su8ZFDPNUCAwEAAaOBpTCB +ojAdBgNVHQ4EFgQUhZV8Mgit2zkFxKk01XoYMehk76owDgYDVR0PAQH/BAQDAgEG +MCMGA1UdEQQcMBqkGDAWMRQwEgYDVQQDEwtTWU1DLTMwNzItMTASBgNVHRMBAf8E +CDAGAQH/AgEAMBcGA1UdIAQQMA4wDAYKKwYBBAGCxC8BATAfBgNVHSMEGDAWgBTM +cmzJEF7WYdxcxCU+wQ6axmPXMjANBgkqhkiG9w0BAQsFAAOCAgEAn/K4jfp7M5Xl +SCYS9htW4EdjCN7v84oCGVc3DFTHalz/wkFVyxhVl1TEw8lWGYG/D97D+fCgPjCa +3PXgOT646tkMqwPQSzUWuTGDYqfsusUqVBl3nXZZqWsRwylaavJAHGK0RvulpVq7 +XeQigrGT1Y0BiRAqGyPq3ai05tMrFSZAGqDVzkJct1zNc+I2eOUEzwJzDgPvTq2o +DN9A6CsHQcjUBGZC086YQmPLbevNo2q6pdxvmSNDT/c6bffPlVRXbweC1iVLWtvP +SPkgDbDqMnGwLFyZcCaqQtRnV1ZCFUjwcPr4zAmXNpYZjIWjh40/giQ7n49hcgtg +Cfjj2uxsq7bBTv+VKRFoytbEP83/Mn0jURaQeNacBly2upqtp6Nt9B2R8UuSB8jH +Jp+NEjaZc+6A1mIkPHkGV9uoLLiXc4wBVP6SFyLmqqCGgAr2VS04KKL5VEXywjBI +goRF2jseS2pJPihaar+LG87ielNFm6h6zl8DTdhMBE7N+Fj4jWU2oUygYs9PLrg3 +f0M2ST9uVybAjPJ8exBIXrN4VJoR6ve6m+mxVkrAgSUa+LUMiqHyixjXmyGlXQIl +72DNqvY8HTTE+P0l6FVC7glcVTyygUv5vVE6pwmm1rbk2upKshQnUelXydYJBsAH +pOR5GJfzMPXZ7V4NjhHPVqJWJQdTsNA= +-----END CERTIFICATE----- diff --git a/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_RCA0002_Cert.pem b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_RCA0002_Cert.pem new file mode 100644 index 0000000000..4ac79b7ad3 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/certs/TEST_OpenADR_RSA_RCA0002_Cert.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_cert.pem b/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_cert.pem new file mode 100644 index 0000000000..852c91d853 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEJDCCAoygAwIBAgIJAOvH0VfgNaeoMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV +BAYTAlVTMRkwFwYDVQQKExBPcGVuQURSIEFsbGlhbmNlMRcwFQYDVQQLEw5SU0Eg +VkVOIENBMDAwMTEpMCcGA1UEAxMgVEVTVCBPcGVuQURSIEFsbGlhbmNlIFJTQSBW +RU4gQ0EwHhcNMTcxMDI0MjA1NzA0WhcNMzcxMDI0MjA1NzA0WjBpMQswCQYDVQQG +EwJVUzERMA8GA1UECgwIS2lzZW5zdW0xMjAwBgNVBAsMKVRFU1QgT3BlbkFEUiBB +bGxpYW5jZSBSU0EgVkVOIENlcnRpZmljYXRlMRMwEQYDVQQDDApraS1ldmkuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/POw6BJ65M0WliNEjPYK +/hq2+L7H5mepllwAMVhQOO8DrRLiAeoLh7RuAuPdAMK05AphWtf87n05kQ13f6Bn +IHYMvp6hlBJ0H8QzQMyVX8wktOnedoQRNzpRAoQeiSrqux2cjvxxmkYrk6qpctbB +IXydniYXP4j8oUpqTWyVlxb0OyDnQ7J8IMq9zbmcJggZsLOAJnE17yfpYSAOWl72 +mV3TiMkF67cEgIO/ioIfcGqWuz4Bbrc1BfXBkU8dCuK956O1K03vfEIiC+ZlHutf +NNu2n0bc+OxioWnEjePRSH2Gz6MnN/3M4I/vxatV8T98FFK+yto5W6yisxahKi7H +5wIDAQABo0wwSjAOBgNVHQ8BAf8EBAMCBaAwHwYDVR0jBBgwFoAUhZV8Mgit2zkF +xKk01XoYMehk76owFwYDVR0gBBAwDjAMBgorBgEEAYLELwEBMA0GCSqGSIb3DQEB +CwUAA4IBgQAg0+PeYS13sCUClEe06BHIn+YwCq0JQDIfTvBsPKBub0tzPly8gLRa +KFgJ0h07jracJc7wraeM1tnVc8HZC0+AWyIoYY8FjCliKfi7/UbwBI9HOS7YL2ew +2TWKvM4PKiIZ6VjQR1SsCyyRDjATnLZBXIr1Kt4A92za8Sr6IXtOwANIWJIp8RME +lWI0SE732FMwgU0QdKPxEwBMvvTZf+jPaEnCODLSg4ynuvKkUuNuLCRKFNuOzeko +Yfl0xIvVsR9pHPkKLUn+D3PkG9oL/ejgpW+lpbdcmjYW5NBLIwBX+3YO1HUDjzAM +lDsbRa4ISk9grojx3DstH1S/ugt81mV2TsETKtDgLGkaQpbGBl/IxYHW9ul0QZtg +8X1M9alcb3ZkFjH8JxZZF11aKAFScvspZvfXi+0g6pj8H3e5oIpiKvY5ExHNx4Nv +6LHq6AMSuvemmhIK2tOqHg4OR3u4udDUBtjQZmOXi5OHHLDZL3uHi+Frg4xi8/AL +GpRyuBf8iLk= +-----END CERTIFICATE----- diff --git a/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_privkey.pem b/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_privkey.pem new file mode 100644 index 0000000000..c011717b7c --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/certs/TEST_RSA_VEN_171024145702_privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD887DoEnrkzRaW +I0SM9gr+Grb4vsfmZ6mWXAAxWFA47wOtEuIB6guHtG4C490AwrTkCmFa1/zufTmR +DXd/oGcgdgy+nqGUEnQfxDNAzJVfzCS06d52hBE3OlEChB6JKuq7HZyO/HGaRiuT +qqly1sEhfJ2eJhc/iPyhSmpNbJWXFvQ7IOdDsnwgyr3NuZwmCBmws4AmcTXvJ+lh +IA5aXvaZXdOIyQXrtwSAg7+Kgh9wapa7PgFutzUF9cGRTx0K4r3no7UrTe98QiIL +5mUe618027afRtz47GKhacSN49FIfYbPoyc3/czgj+/Fq1XxP3wUUr7K2jlbrKKz +FqEqLsfnAgMBAAECggEBAMMQfe0JS0IkMfXH7rYxixqXqWA7Z1ZzvGDaQ7/J1duB +3jjapfVJ8mhNKMULyUMr42kz5A21IDUd1oXYd72xCbYLsxpQpFPWwzw4BjpDSjHc +8LWe4FJO2CPZ0dz52751nRx84l2VqVmKIVK3ugJ4P+k9+yKZRzZUwlTzWPSp9tXS +cjyeREx0JIVNa3baEnNUO71sh0i5A5yY4rzHWMNe28qCl+suZ/l9uhLCCRgagz3C +Gvgcp69LTvttxXf5zw3daGaVlndn57AMhk2xMCIGRyNdJAEp8+Srd/Z4gU2aR1TO +HsK5BqUTaoXR3qmK1kN4G+T9YGDoeGw5ucmhKIDrQAECgYEA/sM/9Lk9qPEE6toO +Qi82SQPBzXIoypoo1lV1x8mfwcP8E8/ns8hhzLXeVGuPbR6YTl3w1vfNhGzoACo7 +keoviKnjCj+fzV+hrMzkxCFmRNLXA17669BO0jnRv4lpUs34YYL/MGJHuAnCk1aj +MqG0cxM/x6y7f6XdLZQ+/nUV5+cCgYEA/i4wmeclUZIu/OWhYuHcT28J4G2jrkiH +JkPvqetk4SLZm/OWIRxPBnEqWGS3ONW6RN5Erq/P/d8uKMnkAnhYHuXWcYuAbX7J +uqLIMsQoRhgP6w4ZSvhTQvxH87lDujL9R2AFko9KtQqHLCnZ5ZzW0l+Xza0coG4r +dIQRx9OEIAECgYBRnzKtcG/t4ZJmwAgTclbdG8QK04l4J93vlZ/Pq6xwgx8PJewh +MJEM6jPaLj/cgRHhAb340ZnZ1J6b9uvw+uvRyERCc1H6laoR1+9vWxgN8tMKdHLF +/I5UUkv+JsNE0VLDwxmJYeOCdjYNyJMu1JogyEkF6zSDWBu9z6JDGjYzfwKBgEdp +2NPUwoJpjPQW3EH47ToQG3lHtJ4p2HbizXozHbO3Y+ZSCNMirf1EXD5bhbLdMalZ +vXhLAyfzxXdGSd57y15a+uYJ6Lpazfi29YJ15E7VQdurqWWL4XDHiruqSKIO42Rj +UQ19XuMAozvAQm0lpKGUpATwZ0OX0HO7C3fWMKABAoGAJ2bjqRIKNBFWLFHZpIo3 +N7CgOSkcTDnHcSiWI5GvFLz51OCpMzBUz9nCKPzzoo9KdDQspKQhq1xjvze7yu4w +nhw1LJ+H/jfQxHP3J34a2s18PPjTTjS3Ww7ohMG6XPNUw8QnyNBZwyhyKdBZdG0n +WIZdQgYu5oI4af3AXg1AelE= +-----END PRIVATE KEY----- diff --git a/services/unsupported/OpenADRVenAgent/config b/services/unsupported/OpenADRVenAgent/config new file mode 100644 index 0000000000..dd854d0f56 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/config @@ -0,0 +1,81 @@ +{ + # VTN server parameters: Kisensum VTN server on AWS host + # + "ven_id": "0", + "ven_name": "ven01", + "vtn_id": "vtn01", + "vtn_address": "http://openadr-vtn.ki-evi.com:8000", + # + # VTN server parameters: Kisensum VTN server on localhost + # + # "ven_id": "0", + # "ven_name": "ven01", + # "vtn_id": "vtn01", + # "vtn_address": "http://localhost:8000", + # + # VTN server parameters: EPRI VTN server on AWS host + # + # "ven_id": "919a1dfa088fe14b79f9", + # "ven_name": "ven_01", + # "vtn_id": "EPRI_VTN", + # "vtn_address": "http://openadr-vtn.ki-evi.com:8080", + # + # Other VEN parameters + # + "db_path": "$VOLTTRON_HOME/data/openadr.sqlite", + "send_registration": "False", + "security_level": "standard", + "poll_interval_secs": 15, + "log_xml": "False", + "opt_in_timeout_secs": 3600, + "opt_in_default_decision": "optOut", + "request_events_on_startup": "False", + # + # VEN reporting configuration + # + "report_parameters": { + "telemetry": { + "report_name": "TELEMETRY_USAGE", + "report_name_metadata": "METADATA_TELEMETRY_USAGE", + "report_specifier_id": "telemetry", + "report_interval_secs_default": "30", + "telemetry_parameters": { + "baseline_power_kw": { + "r_id": "baseline_power", + "report_type": "baseline", + "reading_type": "Direct Read", + "units": "powerReal", + "method_name": "get_baseline_power", + "min_frequency": 30, + "max_frequency": 60 + }, + "current_power_kw": { + "r_id": "actual_power", + "report_type": "reading", + "reading_type": "Direct Read", + "units": "powerReal", + "method_name": "get_current_power", + "min_frequency": 30, + "max_frequency": 60 + } + } + }, + "telemetry_status": { + "report_name": "TELEMETRY_STATUS", + "report_name_metadata": "METADATA_TELEMETRY_STATUS", + "report_specifier_id": "telemetry_status", + "report_interval_secs_default": "300", + "telemetry_parameters": { + "status": { + "r_id": "Status", + "report_type": "x-resourceStatus", + "reading_type": "x-notApplicable", + "units": "", + "method_name": "", + "min_frequency": 60, + "max_frequency": 120 + } + } + } + } +} diff --git a/services/unsupported/OpenADRVenAgent/conftest.py b/services/unsupported/OpenADRVenAgent/conftest.py new file mode 100644 index 0000000000..68e5e611b1 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/conftest.py @@ -0,0 +1,6 @@ +import sys + +from volttrontesting.fixtures.volttron_platform_fixtures import * + +# Add system path of the agent's directory +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) diff --git a/services/unsupported/OpenADRVenAgent/install-ven-agent.sh b/services/unsupported/OpenADRVenAgent/install-ven-agent.sh new file mode 100644 index 0000000000..bae585cdf7 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/install-ven-agent.sh @@ -0,0 +1,8 @@ +cd $VOLTTRON_ROOT +export VIP_SOCKET="ipc://$VOLTTRON_HOME/run/vip.socket" +python scripts/install-agent.py \ + -s $VOLTTRON_ROOT/services/core/OpenADRVenAgent \ + -i venagent \ + -c $VOLTTRON_ROOT/services/core/OpenADRVenAgent/config \ + -t venagent \ + -f diff --git a/services/unsupported/OpenADRVenAgent/openadrven/__init__.py b/services/unsupported/OpenADRVenAgent/openadrven/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/OpenADRVenAgent/openadrven/agent.py b/services/unsupported/OpenADRVenAgent/openadrven/agent.py new file mode 100644 index 0000000000..84365818e7 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/agent.py @@ -0,0 +1,1766 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + + + +from collections import namedtuple +from datetime import datetime as dt +from datetime import timedelta +from dateutil import parser +import gevent +# OpenADR rule 1: use ISO8601 timestamp +import logging +import lxml.etree as etree_ +import os +import random +import requests +from requests.exceptions import ConnectionError +import signxml +import io +import sys + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from volttron.platform.agent import utils +# OpenADR rule 1: use ISO8601 timestamp +from volttron.platform.agent.utils import format_timestamp +from volttron.platform.messaging import topics, headers +from volttron.platform.vip.agent import Agent, Core, RPC +from volttron.platform.scheduling import periodic +from volttron.platform import jsonapi + +from .oadr_builder import * +from .oadr_extractor import * +from .oadr_20b import parseString, oadrSignedObject +from .oadr_common import * +from .models import ORMBase +from .models import EiEvent, EiReport, EiTelemetryValues + +utils.setup_logging() +_log = logging.getLogger(__name__) + +__version__ = '1.0' + +ENDPOINT_BASE = '/OpenADR2/Simple/2.0b/' +EIEVENT = ENDPOINT_BASE + 'EiEvent' +EIREPORT = ENDPOINT_BASE + 'EiReport' +EIREGISTERPARTY = ENDPOINT_BASE + 'EiRegisterParty' +POLL = ENDPOINT_BASE + 'OadrPoll' + +Endpoint = namedtuple('Endpoint', ['url', 'callback']) +OPENADR_ENDPOINTS = { + 'EiEvent': Endpoint(url=EIEVENT, callback='push_request'), + 'EiReport': Endpoint(url=EIREPORT, callback='push_request'), + 'EiRegisterParty': Endpoint(url=EIREGISTERPARTY, callback='push_request')} + +VTN_REQUESTS = { + 'oadrDistributeEvent': 'handle_oadr_distribute_event', + 'oadrRegisterReport': 'handle_oadr_register_report', + 'oadrRegisteredReport': 'handle_oadr_registered_report', + 'oadrCreateReport': 'handle_oadr_create_report', + 'oadrUpdatedReport': 'handle_oadr_updated_report', + 'oadrCancelReport': 'handle_oadr_cancel_report', + 'oadrResponse': 'handle_oadr_response', + 'oadrCreatedPartyRegistration': 'handle_oadr_created_party_registration'} + +PROCESS_LOOP_FREQUENCY_SECS = 5 +DEFAULT_REPORT_INTERVAL_SECS = 15 +DEFAULT_OPT_IN_TIMEOUT_SECS = 30 * 60 # If no optIn timeout was configured, use 30 minutes. + +# These parameters control behavior that is sometimes temporarily disabled during software development. +USE_REPORTS = True +SEND_POLL = True + +# Paths to sample X509 certificates, generated by Kyrio. These are needed when security_level = 'high'. +CERTS_DIRECTORY = '$VOLTTRON_ROOT/services/core/OpenADRVenAgent/certs/' +CERT_FILENAME = CERTS_DIRECTORY + 'TEST_RSA_VEN_171024145702_cert.pem' +KEY_FILENAME = CERTS_DIRECTORY + 'TEST_RSA_VEN_171024145702_privkey.pem' +VTN_CA_CERT_FILENAME = CERTS_DIRECTORY + 'TEST_OpenADR_RSA_BOTH0002_Cert.pem' + + +def ven_agent(config_path, **kwargs): + """ + Parse the OpenADRVenAgent configuration file and return an instance of + the agent that has been created using that configuration. + + See initialize_config() method documentation for a description of each configurable parameter. + + :param config_path: (str) Path to a configuration file. + :returns: OpenADRVenAgent instance + """ + try: + config = utils.load_config(config_path) + except Exception as err: + _log.error("Error loading configuration: {}".format(err)) + config = {} + db_path = config.get('db_path') + ven_id = config.get('ven_id') + ven_name = config.get('ven_name') + vtn_id = config.get('vtn_id') + vtn_address = config.get('vtn_address') + send_registration = config.get('send_registration') + security_level = config.get('security_level') + poll_interval_secs = config.get('poll_interval_secs') + log_xml = config.get('log_xml') + opt_in_timeout_secs = config.get('opt_in_timeout_secs') + opt_in_default_decision = config.get('opt_in_default_decision') + request_events_on_startup = config.get('request_events_on_startup') + report_parameters = config.get('report_parameters') + return OpenADRVenAgent(db_path, ven_id, ven_name, vtn_id, vtn_address, send_registration, security_level, + poll_interval_secs, log_xml, opt_in_timeout_secs, opt_in_default_decision, + request_events_on_startup, report_parameters, **kwargs) + + +class OpenADRVenAgent(Agent): + """ + OpenADR (Automated Demand Response) is a standard for alerting and responding + to the need to adjust electric power consumption in response to fluctuations + in grid demand. + + For further information about OpenADR and this agent, please see + the OpenADR documentation in VOLTTRON ReadTheDocs. + + OpenADR communications are conducted between Virtual Top Nodes (VTNs) and Virtual End Nodes (VENs). + In this implementation, a VOLTTRON agent is a VEN, implementing EiEvent and EiReport services + in conformance with a subset of the OpenADR 2.0b specification. + + The VEN receives VTN requests via the VOLTTRON web service. + + The VTN can 'call an event', indicating that a load-shed event should occur. + The VEN responds with an 'optIn' acknowledgment. + + In conjunction with an event (or independent of events), the VEN reports device status + and usage telemetry, relying on data received periodically from other VOLTTRON agents. + + Events: + The VEN agent maintains a persistent record of DR events. + Event updates (including creation) trigger publication of event JSON on the VOLTTRON message bus. + Other VOLTTRON agents can also call a get_events() RPC to retrieve the current status + of particular events, or of all active events. + + Reporting: + The VEN agent configuration defines telemetry values (data points) to be reported to the VTN. + The VEN agent maintains a persistent record of reportable/reported telemetry values over time. + Other VOLTTRON agents are expected to call a report_telemetry() RPC to supply the VEN agent + with a regular stream of telemetry values for reporting. + Other VOLTTRON agents can receive notification of changes in telemetry reporting requirements + by subscribing to publication of telemetry parameters. + + Pub/Sub (see method documentation): + publish_event() + publish_telemetry_parameters_for_report() + + RPC calls (see method documentation): + respond_to_event(event_id, opt_in=True): + get_events(in_progress_only=True, started_after=None, end_time_before=None) + get_telemetry_parameters() + set_telemetry_status(online, manual_override) + report_telemetry(telemetry_values) + + Supported requests/responses in the OpenADR VTN interface: + VTN: + oadrDistributeEvent (needed for event cancellation) + oadrResponse + oadrRegisteredReport + oadrCreateReport + oadrUpdatedReport + oadrCancelReport + oadrCreatedPartyRegistration + VEN: + oadrPoll + oadrRequestEvent + oadrCreatedEvent + oadrResponse + oadrRegisterReport + oadrCreatedReport + oadrUpdateReport + oadrCanceledReport + oadrCreatePartyRegistration + oadrQueryRegistration + """ + + _db_session = None + _last_poll = None + _active_events = {} + _active_reports = {} + + def __init__(self, db_path, ven_id, ven_name, vtn_id, vtn_address, send_registration, security_level, + poll_interval_secs, log_xml, opt_in_timeout_secs, opt_in_default_decision, + request_events_on_startup, report_parameters, + **kwargs): + super(OpenADRVenAgent, self).__init__(enable_web=True, **kwargs) + + self.db_path = None + self.ven_id = None + self.ven_name = None + self.vtn_id = None + self.vtn_address = None + self.send_registration = False + self.security_level = None + self.poll_interval_secs = None + self.log_xml = True + self.opt_in_timeout_secs = None + self.opt_in_default_decision = 'optIn' + self.request_events_on_startup = None + self.report_parameters = {} + self.default_config = {"db_path": db_path, + "ven_id": ven_id, + "ven_name": ven_name, + "vtn_id": vtn_id, + "vtn_address": vtn_address, + "send_registration": send_registration, + "security_level": security_level, + "poll_interval_secs": poll_interval_secs, + "log_xml": log_xml, + "opt_in_timeout_secs": opt_in_timeout_secs, + "opt_in_default_decision": opt_in_default_decision, + "request_events_on_startup": request_events_on_startup, + "report_parameters": report_parameters} + self.vip.config.set_default("config", self.default_config) + self.vip.config.subscribe(self._configure, actions=["NEW", "UPDATE"], pattern="config") + self.initialize_config(self.default_config) + # State variables for VTN request/response processing + self.oadr_current_service = None + self.oadr_current_request_id = None + # The following parameters can be adjusted by issuing a set_telemetry_status() RPC call. + self.ven_online = 'false' + self.ven_manual_override = 'false' + + def _configure(self, config_name, action, contents): + """The agent's config may have changed. Re-initialize it.""" + config = self.default_config.copy() + config.update(contents) + self.initialize_config(config) + + def initialize_config(self, config): + """ + Initialize the agent's configuration. + + Configuration parameters (see config for a sample config file): + + db_path: Pathname of the agent's sqlite database. + ~ and shell variables will be expanded if present. + ven_id: (string) OpenADR ID of this virtual end node. Identifies this VEN to the VTN. + ven_name: Name of this virtual end node. Identifies this VEN during registration, + before its ID is known. + vtn_id: (string) OpenADR ID of the VTN with which this VEN communicates. + vtn_address: URL and port number of the VTN. + send_registration: ('True' or 'False') If 'True', send a one-time registration request to the VTN, + obtaining the VEN ID. The agent should be run in this mode initially, + then shut down and run with this parameter set to 'False' thereafter. + security_level: If 'high', the VTN and VEN use a third-party signing authority to sign + and authenticate each request. + Default is 'standard' (XML payloads do not contain Signature elements). + poll_interval_secs: (integer) How often the VEN should send an OadrPoll to the VTN. + log_xml: ('True' or 'False') Whether to write inbound/outbound XML to the agent's log. + opt_in_timeout_secs: (integer) How long to wait before making a default optIn/optOut decision. + opt_in_default_decision: ('True' or 'False') What optIn/optOut choice to make by default. + request_events_on_startup: ('True' or 'False') Whether to send oadrRequestEvent to the VTN on startup. + report_parameters: A dictionary of definitions of reporting/telemetry parameters. + """ + _log.debug("Configuring agent") + self.db_path = config.get('db_path') + self.ven_id = config.get('ven_id') + self.ven_name = config.get('ven_name') + self.vtn_id = config.get('vtn_id') + self.vtn_address = config.get('vtn_address') + self.send_registration = (config.get('send_registration') == 'True') + self.security_level = config.get('security_level') + self.log_xml = (config.get('log_xml') != 'False') + opt_in_timeout = config.get('opt_in_timeout_secs') + self.opt_in_timeout_secs = int(opt_in_timeout if opt_in_timeout else DEFAULT_OPT_IN_TIMEOUT_SECS) + self.opt_in_default_decision = config.get('opt_in_default_decision') + loop_frequency = config.get('poll_interval_secs') + self.poll_interval_secs = int(loop_frequency if loop_frequency else PROCESS_LOOP_FREQUENCY_SECS) + self.request_events_on_startup = (config.get('request_events_on_startup') == 'True') + self.report_parameters = config.get('report_parameters') + + # Validate and adjust the configuration parameters. + if type(self.db_path) == str: + self.db_path = os.path.expanduser(self.db_path) + self.db_path = os.path.expandvars(self.db_path) + try: + self.opt_in_timeout_secs = int(self.opt_in_timeout_secs) + except ValueError: + # If opt_in_timeout_secs was not supplied or was not an integer, default to a 10-minute timeout. + self.opt_in_timeout_secs = 600 + + if self.poll_interval_secs < PROCESS_LOOP_FREQUENCY_SECS: + _log.warning('Poll interval is too frequent: resetting it to {}'.format(PROCESS_LOOP_FREQUENCY_SECS)) + self.poll_interval_secs = PROCESS_LOOP_FREQUENCY_SECS + + _log.info('Configuration parameters:') + _log.info('\tDatabase = {}'.format(self.db_path)) + _log.info('\tVEN ID = {}'.format(self.ven_id)) + _log.info('\tVEN name = {}'.format(self.ven_name)) + _log.info('\tVTN ID = {}'.format(self.vtn_id)) + _log.info('\tVTN address = {}'.format(self.vtn_address)) + _log.info('\tSend registration = {}'.format(self.send_registration)) + _log.info('\tSecurity level = {}'.format(self.security_level)) + _log.info('\tPoll interval = {} seconds'.format(self.poll_interval_secs)) + _log.info('\tLog XML = {}'.format(self.log_xml)) + _log.info('\toptIn timeout (secs) = {}'.format(self.opt_in_timeout_secs)) + _log.info('\toptIn default decision = {}'.format(self.opt_in_default_decision)) + _log.info('\tRequest events on startup = {}'.format(self.request_events_on_startup)) + _log.info("\treport parameters = {}".format(self.report_parameters)) + + @Core.receiver('onstart') + def onstart_method(self, sender): + """The agent has started. Perform initialization and spawn the main process loop.""" + _log.debug('Starting agent') + + self.register_endpoints() + + if self.send_registration: + # VEN registration with the VTN server. + # Register the VEN, obtaining the VEN ID. This is a one-time action. + self.send_oadr_create_party_registration() + else: + # Schedule an hourly database-cleanup task. + self.core.schedule(periodic(60 * 60), self.telemetry_cleanup) + + # Populate the caches with all of the database's events and reports that are active. + for event in self._get_events(): + _log.debug('Re-caching event with ID {}'.format(event.event_id)) + self._active_events[event.event_id] = event + for report in self._get_reports(): + _log.debug('Re-caching report with ID {}'.format(report.report_request_id)) + self._active_reports[report.report_request_id] = report + + try: + if self.request_events_on_startup: + # After a restart, the VEN asks the VTN for the status of all current events. + # When this is sent to the EPRI VTN server, it returns a 500 and logs a "method missing" traceback. + self.send_oadr_request_event() + + if USE_REPORTS: + # Send an initial report-registration request to the VTN. + self.send_oadr_register_report() + except Exception as err: + _log.error('Error in agent startup: {}'.format(err), exc_info=True) + self.core.schedule(periodic(PROCESS_LOOP_FREQUENCY_SECS), self.main_process_loop) + + def main_process_loop(self): + """ + gevent thread. Perform periodic tasks, executing them serially. + + Periodic tasks include: + Poll the VTN server. + Perform event-management tasks: + Force an optIn/optOut decision if too much time has elapsed. + Transition event state when appropriate. + Expire events that have become completed or canceled. + Perform report-management tasks: + Send telemetry to the VTN for any active report. + Transition report state when appropriate. + Expire reports that have become completed or canceled. + + This is intended to be a long-running gevent greenlet -- it should never crash. + If exceptions occur, they are logged, but no process failure occurs. + """ + try: + # If it's been poll_interval_secs since the last poll request, issue a new one. + if self._last_poll is None or \ + ((utils.get_aware_utc_now() - self._last_poll).total_seconds() > self.poll_interval_secs): + if SEND_POLL: + self.send_oadr_poll() + + for event in self.active_events(): + self.process_event(event) + + if USE_REPORTS: + for report in self.active_reports(): + self.process_report(report) + + except Exception as err: + _log.error('Error in main process loop: {}'.format(err), exc_info=True) + + def process_event(self, evt): + """ + Perform periodic maintenance for an event that's in the cache. + + Transition its state when appropriate. + Expire it from the cache if it has become completed or canceled. + + @param evt: An EiEvent instance. + """ + now = utils.get_aware_utc_now() + if evt.is_active(): + if evt.end_time is not None and now > evt.end_time: + _log.debug('Setting event {} to status {}'.format(evt.event_id, evt.STATUS_COMPLETED)) + self.set_event_status(evt, evt.STATUS_COMPLETED) + self.publish_event(evt) + else: + if evt.status == evt.STATUS_ACTIVE: + # It's an active event. Which is fine; nothing special needs to be done here. + pass + else: + if now > evt.start_time and evt.opt_type == evt.OPT_TYPE_OPT_IN: + _log.debug('Setting event {} to status {}'.format(evt.event_id, evt.STATUS_ACTIVE)) + self.set_event_status(evt, evt.STATUS_ACTIVE) + self.publish_event(evt) + else: + # Expire events from the cache if they're completed or canceled. + _log.debug('Expiring event {}'.format(evt.event_id)) + self.expire_event(evt) + + def process_report(self, rpt): + """ + Perform periodic maintenance for a report that's in the cache. + + Send telemetry to the VTN if the report is active. + Transition its state when appropriate. + Expire it from the cache if it has become completed or canceled. + + @param rpt: An EiReport instance. + """ + if rpt.is_active(): + now = utils.get_aware_utc_now() + if rpt.status == rpt.STATUS_ACTIVE: + if rpt.end_time is None or rpt.end_time > now: + rpt_interval = rpt.interval_secs if rpt.interval_secs is not None else DEFAULT_REPORT_INTERVAL_SECS + next_report_time = rpt.last_report + timedelta(seconds=rpt_interval) + if utils.get_aware_utc_now() > next_report_time: + # Possible enhancement: Use a periodic gevent instead of a timeout? + self.send_oadr_update_report(rpt) + if rpt_interval == 0: + # OADR rule 324: If rpt_interval == 0 it's a one-time report, so set status to COMPLETED. + rpt.status = rpt.STATUS_COMPLETED + self.commit() + else: + _log.debug('Setting report {} to status {}'.format(rpt.report_request_id, rpt.STATUS_COMPLETED)) + self.set_report_status(rpt, rpt.STATUS_COMPLETED) + self.publish_telemetry_parameters_for_report(rpt) + else: + if rpt.start_time < now and (rpt.end_time is None or now < rpt.end_time): + _log.debug('Setting report {} to status {}'.format(rpt.report_request_id, rpt.STATUS_ACTIVE)) + self.set_report_status(rpt, rpt.STATUS_ACTIVE) + self.publish_telemetry_parameters_for_report(rpt) + else: + # Expire reports from the cache if they're completed or canceled. + _log.debug('Expiring report {} from cache'.format(rpt.report_request_id)) + self.expire_event(rpt) + + def force_opt_type_decision(self, event_id): + """ + Force an optIn/optOut default decision if lots of time has elapsed with no decision from the control agent. + + Scheduled gevent thread, kicked off when an event is first published. + The default choice comes from "opt_in_default_decision" in the agent config. + + @param event_id: (String) ID of the event for which a decision will be made. + """ + event = self.get_event_for_id(event_id) + if event and event.is_active() and event.opt_type not in [EiEvent.OPT_TYPE_OPT_IN, + EiEvent.OPT_TYPE_OPT_OUT]: + event.opt_type = self.opt_in_default_decision + self.commit() + _log.info('Forcing an {} decision for event {}'.format(event.opt_type, event.event_id)) + if event.status == event.STATUS_ACTIVE: + # Odd exception scenario: If the event was already active, roll its status back to STATUS_FAR. + self.set_event_status(event, event.STATUS_FAR) + self.publish_event(event) # Tell the volttron message bus. + self.send_oadr_created_event(event) # Tell the VTN. + + # ***************** Methods for Servicing VTN Requests ******************** + + def push_request(self, env, request): + """Callback. The VTN pushed an http request. Service it.""" + _log.debug('Servicing a VTN push request') + self.core.spawn(self.service_vtn_request, request) + # Return an empty response. + return [HTTP_STATUS_CODES[204], '', [("Content-Length", "0")]] + + def service_vtn_request(self, request): + """ + An HTTP request/response was received. Handle it. + + Event workflow (see OpenADR Profile Specification section 8.1)... + + Event poll / creation: + (VEN) oadrPoll + (VTN) oadrDistributeEvent (all events are included; one oadrEvent element per event) + (VEN) oadrCreatedEvent with optIn/optOut (if events had oadrResponseRequired) + If "always", an oadrCreatedEvent must be sent for each event. + If "never", it was a "broadcast" event -- never create an event in response. + Otherwise, respond if event state (eventID, modificationNumber) has changed. + (VTN) oadrResponse + + Event change: + (VEN) oadrCreatedEvent (sent if the optIn/optOut status has changed) + (VTN) oadrResponse + + Sample oadrDistributeEvent use case from the OpenADR Program Guide: + + Event: + Notification: Day before event + Start Time: midnight + Duration: 24 hours + Randomization: None + Ramp Up: None + Recovery: None + Number of signals: 2 + Signal Name: simple + Signal Type: level + Units: LevN/A + Number of intervals: equal TOU Tier change in 24 hours (2 - 6) + Interval Duration(s): TOU tier active time frame (i.e. 6 hours) + Typical Interval Value(s): 0 - 4 mapped to TOU Tiers (0 - Cheapest Tier) + Signal Target: None + Signal Name: ELECTRICITY_PRICE + Signal Type: price + Units: USD per Kwh + Number of intervals: equal TOU Tier changes in 24 hours (2 - 6) + Interval Duration(s): TOU tier active time frame (i.e. 6 hours) + Typical Interval Value(s): $0.10 to $1.00 (current tier rate) + Signal Target: None + Event Targets: venID_1234 + Priority: 1 + VEN Response Required: always + VEN Expected Response: optIn + Reports: + None + + Report workflow (see OpenADR Profile Specification section 8.3)... + + Report registration interaction: + (VEN) oadrRegisterReport (METADATA report) + VEN sends its reporting capabilities to VTN. + Each report, identified by a reportSpecifierID, is described as elements and attributes. + (VTN) oadrRegisteredReport (with optional oadrReportRequests) + VTN acknowledges that capabilities have been registered. + VTN optionally requests one or more reports by reportSpecifierID. + Even if reports were previously requested, they should be requested again at this point. + (VEN) oadrCreatedReport (if report requested) + VEN acknowledges that it has received the report request and is generating the report. + If any reports were pending delivery, they are included in the payload. + (VTN) oadrResponse + Why?? + + Report creation interaction: + (VTN) oadrCreateReport + See above - this is like the "request" portion of oadrRegisteredReport + (VEN) oadrCreatedReport + See above. + + Report update interaction - this is the actual report: + (VEN) oadrUpdateReport (report with reportRequestID and reportSpecifierID) + Send a report update containing actual data values + (VTN) oadrUpdatedReport (optional oadrCancelReport) + Acknowledge report receipt, and optionally cancel the report + + Report cancellation: + (VTN) oadrCancelReport (reportRequestID) + This can be sent to cancel a report that is in progress. + It should also be sent if the VEN keeps sending oadrUpdateReport + after an oadrUpdatedReport cancellation. + If reportToFollow = True, the VEN is expected to send one final additional report. + (VEN) oadrCanceledReport + Acknowledge the cancellation. + If any reports were pending delivery, they are included in the payload. + + Key elements in the METADATA payload: + reportSpecifierID: Report identifier, used by subsequent oadrCreateReport requests + rid: Data point identifier + This VEN reports only two data points: baselinePower, actualPower + Duration: the amount of time that data can be collected + SamplingRate.oadrMinPeriod: maximum sampling frequency + SamplingRate.oadrMaxPeriod: minimum sampling frequency + SamplingRate.onChange: whether or not data is sampled as it changes + + For an oadrCreateReport example from the OpenADR Program Guide, see test/xml/sample_oadrCreateReport.xml. + + @param request: The request's XML payload. + """ + try: + if self.log_xml: + _log.debug('VTN PAYLOAD:') + _log.debug('\n{}'.format(etree_.tostring(etree_.fromstring(request), pretty_print=True))) + payload = parseString(request, silence=True) + signed_object = payload.oadrSignedObject + if signed_object is None: + raise OpenADRInterfaceException('No SignedObject in payload', OADR_BAD_DATA) + + if self.security_level == 'high': + # At high security, the request is accompanied by a Signature. + # (not implemented) The VEN should use a certificate authority to validate and decode the request. + pass + + # Call an appropriate method to handle the VTN request. + element_name = self.vtn_request_element_name(signed_object) + _log.debug('VTN: {}'.format(element_name)) + request_object = getattr(signed_object, element_name) + request_method = getattr(self, VTN_REQUESTS[element_name]) + request_method(request_object) + + if request_object.__class__.__name__ != 'oadrResponseType': + # A non-default response was received from the VTN. Issue a followup poll request. + self.send_oadr_poll() + + except OpenADRInternalException as err: + if err.error_code == OADR_EMPTY_DISTRIBUTE_EVENT: + _log.warning('Error handling VTN request: {}'.format(err)) # No need for a stack trace + else: + _log.warning('Error handling VTN request: {}'.format(err), exc_info=True) + except OpenADRInterfaceException as err: + _log.warning('Error handling VTN request: {}'.format(err), exc_info=True) + # OADR rule 48: Log the validation failure, send an oadrResponse.eiResponse with an error code. + self.send_oadr_response(err, err.error_code or OADR_BAD_DATA) + except Exception as err: + _log.error("Error handling VTN request: {}".format(err), exc_info=True) + self.send_oadr_response(err, OADR_BAD_DATA) + + @staticmethod + def vtn_request_element_name(signed_object): + """Given a SignedObject from the VTN, return the element name of the request that it wraps.""" + non_null_elements = [name for name in VTN_REQUESTS.keys() if getattr(signed_object, name)] + element_count = len(non_null_elements) + if element_count == 1: + return non_null_elements[0] + else: + if element_count == 0: + error_msg = 'Bad request {}, supported types are {}'.format(signed_object, VTN_REQUESTS.keys()) + else: + error_msg = 'Bad request {}, too many signedObject elements'.format(signed_object) + raise OpenADRInterfaceException(error_msg, None) + + # ***************** Handle Requests from the VTN to the VEN ******************** + + def handle_oadr_created_party_registration(self, oadr_created_party_registration): + """ + The VTN has responded to an oadrCreatePartyRegistration by sending an oadrCreatedPartyRegistration. + + @param oadr_created_party_registration: The VTN's request. + """ + self.oadr_current_service = EIREGISTERPARTY + self.check_ei_response(oadr_created_party_registration.eiResponse) + extractor = OadrCreatedPartyRegistrationExtractor(registration=oadr_created_party_registration) + _log.info('***********') + ven_id = extractor.extract_ven_id() + if ven_id: + _log.info('The VTN supplied {} as the ID of this VEN (ven_id).'.format(ven_id)) + poll_freq = extractor.extract_poll_freq() + if poll_freq: + _log.info('The VTN requested a poll frequency of {} (poll_interval_secs).'.format(poll_freq)) + vtn_id = extractor.extract_vtn_id() + if vtn_id: + _log.info('The VTN supplied {} as its ID (vtn_id).'.format(vtn_id)) + _log.info('Please set these values in the VEN agent config.') + _log.info('Registration is complete. Set send_registration to False in the VEN config and restart the agent.') + _log.info('***********') + + def handle_oadr_distribute_event(self, oadr_distribute_event): + """ + The VTN has responded to an oadrPoll by sending an oadrDistributeEvent. + + Create or update an event, then respond with oadrCreatedEvent. + + For sample XML, see test/xml/sample_oadrDistributeEvent.xml. + + @param oadr_distribute_event: (OadrDistributeEventType) The VTN's request. + """ + self.oadr_current_service = EIEVENT + self.oadr_current_request_id = None + if getattr(oadr_distribute_event, 'eiResponse'): + self.check_ei_response(oadr_distribute_event.eiResponse) + + # OADR rule 41: requestID does not need to be unique. + self.oadr_current_request_id = oadr_distribute_event.requestID + + vtn_id = oadr_distribute_event.vtnID + if vtn_id is not None and vtn_id != self.vtn_id: + raise OpenADRInterfaceException('vtnID failed to match agent config: {}'.format(vtn_id), OADR_BAD_DATA) + + oadr_event_list = oadr_distribute_event.oadrEvent + if len(oadr_event_list) == 0: + raise OpenADRInternalException('oadrDistributeEvent received with no events', OADR_EMPTY_DISTRIBUTE_EVENT) + + oadr_event_ids = [] + for oadr_event in oadr_event_list: + try: + event = self.handle_oadr_event(oadr_event) + if event: + oadr_event_ids.append(event.event_id) + except OpenADRInterfaceException as err: + # OADR rule 19: If a VTN message contains a mix of valid and invalid events, + # respond to the valid ones. Don't reject the entire message due to invalid events. + # OADR rule 48: Log the validation failure and send the error code in oadrCreatedEvent.eventResponse. + # (The oadrCreatedEvent's eiResponse should contain a 200 -- normal -- status code.) + _log.warning('Event error: {}'.format(err), exc_info=True) + # Construct a temporary EIEvent to hold data that will be reported in the error return. + if oadr_event.eiEvent and oadr_event.eiEvent.eventDescriptor: + event_id = oadr_event.eiEvent.eventDescriptor.eventID + modification_number = oadr_event.eiEvent.eventDescriptor.modificationNumber + else: + event_id = None + modification_number = None + error_event = EiEvent(self.oadr_current_request_id, event_id) + error_event.modification_number = modification_number + self.send_oadr_created_event(error_event, + error_code=err.error_code or OADR_BAD_DATA, + error_message=err) + except Exception as err: + _log.warning('Unanticipated error during event processing: {}'.format(err), exc_info=True) + self.send_oadr_response(err, OADR_BAD_DATA) + + for agent_event in self._get_events(): + if agent_event.event_id not in oadr_event_ids: + # "Implied cancel:" + # OADR rule 61: If the VTN request omitted an active event, cancel it. + # Also, think about whether to alert the VTN about this cancellation by sending it an oadrCreatedEvent. + _log.debug('Event ID {} not in distributeEvent: canceling it.'.format(agent_event.event_id)) + self.handle_event_cancellation(agent_event, 'never') + + def handle_oadr_event(self, oadr_event): + """ + An oadrEvent was received, usually as part of an oadrDistributeEvent. Handle the event creation/update. + + Respond with oadrCreatedEvent. + + For sample XML, see test/xml/sample_oadrDistributeEvent.xml. + + @param oadr_event: (OadrEventType) The VTN's request. + @return: (EiEvent) The event that was created or updated. + """ + + def create_temp_event(received_ei_event): + """Create a temporary EiEvent in preparation for an event creation or update.""" + event_descriptor = received_ei_event.eventDescriptor + if event_descriptor is None: + raise OpenADRInterfaceException('Missing eiEvent.eventDescriptor', OADR_BAD_DATA) + event_id = event_descriptor.eventID + if event_id is None: + raise OpenADRInterfaceException('Missing eiEvent.eventDescriptor.eventID', OADR_BAD_DATA) + _log.debug('Processing received event, ID = {}'.format(event_id)) + tmp_event = EiEvent(self.oadr_current_request_id, event_id) + extractor = OadrEventExtractor(event=tmp_event, ei_event=received_ei_event) + extractor.extract_event_descriptor() + extractor.extract_active_period() + extractor.extract_signals() + return tmp_event + + def update_event(temp_event, event): + """Update the current event based on the contents of temp_event.""" + _log.debug('Modification number has changed: {}'.format(temp_event.modification_number)) + # OADR rule 57: If modificationNumber increments, replace the event with the modified version. + if event.opt_type == EiEvent.OPT_TYPE_OPT_OUT: + # OADR rule 50: The VTN may continue to send events that the VEN has opted out of. + pass # Take no action, other than responding to the VTN. + else: + if temp_event.status == EiEvent.STATUS_CANCELED: + if event.status != EiEvent.STATUS_CANCELED: + # OADR rule 59: The event was just canceled. Process an event cancellation. + self.handle_event_cancellation(event, response_required) + else: + event.copy_from_event(temp_event) + # A VEN may ignore the received event status, calculating it based on the time. + # OADR rule 66: Do not treat status == completed as a cancellation. + if event.status == EiEvent.STATUS_CANCELED and temp_event.status != EiEvent.STATUS_CANCELED: + # If the VEN thinks the event is canceled and the VTN doesn't think that, un-cancel it. + event.status = temp_event.status + self.commit() + # Tell the VOLTTRON world about the event update. + self.publish_event(event) + + def create_event(event): + self.add_event(event) + if event.status == EiEvent.STATUS_CANCELED: + # OADR rule 60: Ignore a new event if it's cancelled - this is NOT a validation error. + pass + else: + opt_deadline = utils.get_aware_utc_now() + timedelta(seconds=self.opt_in_timeout_secs) + self.core.schedule(opt_deadline, self.force_opt_type_decision, event.event_id) + _log.debug('Scheduled a default optIn/optOut decision for {}'.format(opt_deadline)) + self.publish_event(event) # Tell the VOLTTRON world about the event creation. + + # Create a temporary EiEvent, constructed from the OadrDistributeEventType. + ei_event = oadr_event.eiEvent + response_required = oadr_event.oadrResponseRequired + + if ei_event.eiTarget and ei_event.eiTarget.venID and self.ven_id not in ei_event.eiTarget.venID: + # Rule 22: If an eiTarget is furnished, handle the event only if this venID is in the target list. + event = None + else: + temp_event = create_temp_event(ei_event) + event = self.get_event_for_id(temp_event.event_id) + if event: + if temp_event.modification_number < event.modification_number: + _log.debug('Out-of-order modification number: {}'.format(temp_event.modification_number)) + # OADR rule 58: Respond with error code 450. + raise OpenADRInterfaceException('Invalid modification number (too low)', + OADR_MOD_NUMBER_OUT_OF_ORDER) + elif temp_event.modification_number > event.modification_number: + update_event(temp_event, event) + else: + _log.debug('No modification number change, taking no action') + else: + # OADR rule 56: If the received event has an unrecognized event_id, create a new event. + _log.debug('Creating event for ID {}'.format(temp_event.event_id)) + event = temp_event + create_event(event) + + if response_required == 'always': + # OADR rule 12, 62: Send an oadrCreatedEvent if response_required == 'always'. + # OADR rule 12, 62: If response_required == 'never', do not send an oadrCreatedEvent. + self.send_oadr_created_event(event) + + return event + + def handle_event_cancellation(self, event, response_required): + """ + An event was canceled by the VTN. Update local state and publish the news. + + @param event: (EiEvent) The event that was canceled. + @param response_required: (string) Indicates when the VTN expects a confirmation/response to its request. + """ + if event.start_after: + # OADR rule 65: If the event has a startAfter value, + # schedule cancellation for a random future time between now and (now + startAfter). + max_delay = isodate.parse_duration(event.start_after) + cancel_time = utils.get_aware_utc_now() + timedelta(seconds=(max_delay.seconds * random.random())) + self.core.schedule(cancel_time, self._handle_event_cancellation, event, response_required) + else: + self._handle_event_cancellation(event, response_required) + + def _handle_event_cancellation(self, event, response_required): + """ + (Internal) An event was canceled by the VTN. Update local state and publish the news. + + @param event: (EiEvent) The event that was canceled. + @param response_required: (string) Indicates when the VTN expects a confirmation/response to its request. + """ + event.status = EiEvent.STATUS_CANCELED + if response_required != 'never': + # OADR rule 36: If response_required != never, confirm cancellation with optType = optIn. + event.optType = event.OPT_TYPE_OPT_IN + self.commit() + self.publish_event(event) # Tell VOLTTRON agents about the cancellation. + + def handle_oadr_register_report(self, request): + """ + The VTN is sending METADATA, registering the reports that it can send to the VEN. + + Send no response -- the VEN doesn't want any of the VTN's crumby reports. + + @param request: The VTN's request. + """ + self.oadr_current_service = EIREPORT + self.oadr_current_request_id = None + # OADR rule 301: Sent when the VTN wakes up. + pass + + def handle_oadr_registered_report(self, oadr_registered_report): + """ + The VTN acknowledged receipt of the METADATA in oadrRegisterReport. + + If the VTN requested any reports (by specifier ID), create them. + Send an oadrCreatedReport acknowledgment for each request. + + @param oadr_registered_report: (oadrRegisteredReportType) The VTN's request. + """ + self.oadr_current_service = EIREPORT + self.check_ei_response(oadr_registered_report.eiResponse) + self.create_or_update_reports(oadr_registered_report.oadrReportRequest) + + def handle_oadr_create_report(self, oadr_create_report): + """ + Handle an oadrCreateReport request from the VTN. + + The request could have arrived in response to a poll, + or it could have been part of an oadrRegisteredReport response. + + Create a report for each oadrReportRequest in the list, sending an oadrCreatedReport in response. + + @param oadr_create_report: The VTN's oadrCreateReport request. + """ + self.oadr_current_service = EIREPORT + self.oadr_current_request_id = None + self.create_or_update_reports(oadr_create_report.oadrReportRequest) + + def handle_oadr_updated_report(self, oadr_updated_report): + """ + The VTN acknowledged receipt of an oadrUpdatedReport, and may have sent a report cancellation. + + Check for report cancellation, and cancel the report if necessary. No need to send a response to the VTN. + + @param oadr_updated_report: The VTN's request. + """ + self.oadr_current_service = EIREPORT + self.check_ei_response(oadr_updated_report.eiResponse) + oadr_cancel_report = oadr_updated_report.oadrCancelReport + if oadr_cancel_report: + self.cancel_report(oadr_cancel_report.reportRequestID, acknowledge=False) + + def handle_oadr_cancel_report(self, oadr_cancel_report): + """ + The VTN responded to an oadrPoll by requesting a report cancellation. + + Respond by canceling the report, then send oadrCanceledReport to the VTN. + + @param oadr_cancel_report: (oadrCancelReportType) The VTN's request. + """ + self.oadr_current_service = EIREPORT + self.oadr_current_request_id = oadr_cancel_report.requestID + self.cancel_report(oadr_cancel_report.reportRequestID, acknowledge=True) + + def handle_oadr_response(self, oadr_response): + """ + The VTN has acknowledged a VEN request such as oadrCreatedReport. + + No response is needed. + + @param oadr_response: The VTN's request. + """ + self.check_ei_response(oadr_response.eiResponse) + + def check_ei_response(self, ei_response): + """ + An eiResponse can appear in multiple kinds of VTN requests. + + If an eiResponse has been received, check for a '200' (OK) response code. + If any other code is received, the VTN is reporting an error -- log it and raise an exception. + + @param ei_response: (eiResponseType) The VTN's eiResponse. + """ + self.oadr_current_request_id = ei_response.requestID + response_code, response_description = OadrResponseExtractor(ei_response=ei_response).extract() + if response_code != OADR_VALID_RESPONSE: + error_text = 'Error response from VTN, code={}, description={}'.format(response_code, response_description) + _log.error(error_text) + raise OpenADRInternalException(error_text, response_code) + + def create_or_update_reports(self, report_list): + """ + Process report creation/update requests from the VTN (which could have arrived in different payloads). + + The requests could have arrived in response to a poll, + or they could have been part of an oadrRegisteredReport response. + + Create/Update reports, and publish info about them on the volttron message bus. + Send an oadrCreatedReport response to the VTN for each report. + + @param report_list: A list of oadrReportRequest. Can be None. + """ + + def create_temp_rpt(report_request): + """Validate the report request, creating a temporary EiReport instance in the process.""" + extractor = OadrReportExtractor(request=report_request) + tmp_report = EiReport(None, + extractor.extract_report_request_id(), + extractor.extract_specifier_id()) + rpt_params = self.report_parameters.get(tmp_report.report_specifier_id, None) + if rpt_params is None: + err_msg = 'No parameters found for report with specifier ID {}'.format(tmp_report.report_specifier_id) + _log.error(err_msg) + raise OpenADRInterfaceException(err_msg, OADR_BAD_DATA) + extractor.report_parameters = rpt_params + extractor.report = tmp_report + extractor.extract_report() + return tmp_report + + def update_rpt(tmp_rpt, rpt): + """If the report changed, update its parameters in the database, and publish them on the message bus.""" + if rpt.report_specifier_id != tmp_rpt.report_specifier_id \ + or rpt.start_time != tmp_rpt.start_time \ + or rpt.end_time != tmp_rpt.end_time \ + or rpt.interval_secs != tmp_rpt.interval_secs: + rpt.copy_from_report(tmp_rpt) + self.commit() + self.publish_telemetry_parameters_for_report(rpt) + + def create_rpt(tmp_rpt): + """Store the new report request in the database, and publish it on the message bus.""" + self.add_report(tmp_rpt) + self.publish_telemetry_parameters_for_report(tmp_rpt) + + def cancel_rpt(rpt): + """A report cancellation was received. Process it and notify interested parties.""" + rpt.status = rpt.STATUS_CANCELED + self.commit() + self.publish_telemetry_parameters_for_report(rpt) + + oadr_report_request_ids = [] + + try: + if report_list: + for oadr_report_request in report_list: + temp_report = create_temp_rpt(oadr_report_request) + existing_report = self.get_report_for_report_request_id(temp_report.report_request_id) + if temp_report.status == temp_report.STATUS_CANCELED: + if existing_report: + oadr_report_request_ids.append(temp_report.report_request_id) + cancel_rpt(existing_report) + self.send_oadr_created_report(oadr_report_request) + else: + # Received notification of a new report, but it's already canceled. Take no action. + pass + else: + oadr_report_request_ids.append(temp_report.report_request_id) + if temp_report.report_specifier_id == 'METADATA': + # Rule 301/327: If the request's specifierID is 'METADATA', send an oadrRegisterReport. + self.send_oadr_created_report(oadr_report_request) + self.send_oadr_register_report() + elif existing_report: + update_rpt(temp_report, existing_report) + self.send_oadr_created_report(oadr_report_request) + else: + create_rpt(temp_report) + self.send_oadr_created_report(oadr_report_request) + except OpenADRInterfaceException as err: + # If a VTN message contains a mix of valid and invalid reports, respond to the valid ones. + # Don't reject the entire message due to an invalid report. + _log.warning('Report error: {}'.format(err), exc_info=True) + self.send_oadr_response(err, err.error_code or OADR_BAD_DATA) + except Exception as err: + _log.warning('Unanticipated error during report processing: {}'.format(err), exc_info=True) + self.send_oadr_response(err, OADR_BAD_DATA) + + all_active_reports = self._get_reports() + for agent_report in all_active_reports: + if agent_report.report_request_id not in oadr_report_request_ids: + # If the VTN's request omitted an active report, treat it as an implied cancellation. + report_request_id = agent_report.report_request_id + _log.debug('Report request ID {} not sent by VTN, canceling the report.'.format(report_request_id)) + self.cancel_report(report_request_id, acknowledge=True) + + def cancel_report(self, report_request_id, acknowledge=False): + """ + The VTN asked to cancel a report, in response to either report telemetry or an oadrPoll. Cancel it. + + @param report_request_id: (string) The report_request_id of the report to be canceled. + @param acknowledge: (boolean) If True, send an oadrCanceledReport acknowledgment to the VTN. + """ + if report_request_id is None: + raise OpenADRInterfaceException('Missing oadrCancelReport.reportRequestID', OADR_BAD_DATA) + report = self.get_report_for_report_request_id(report_request_id) + if report: + report.status = report.STATUS_CANCELED + self.commit() + self.publish_telemetry_parameters_for_report(report) + if acknowledge: + self.send_oadr_canceled_report(report_request_id) + else: + # The VEN got asked to cancel a report that it doesn't have. Do nothing. + pass + + # ***************** Send Requests from the VEN to the VTN ******************** + + def send_oadr_poll(self): + """Send oadrPoll to the VTN.""" + _log.debug('VEN: oadrPoll') + self.oadr_current_service = POLL + # OADR rule 37: The VEN must support the PULL implementation. + self._last_poll = utils.get_aware_utc_now() + self.send_vtn_request('oadrPoll', OadrPollBuilder(ven_id=self.ven_id).build()) + + def send_oadr_query_registration(self): + """Send oadrQueryRegistration to the VTN.""" + _log.debug('VEN: oadrQueryRegistration') + self.oadr_current_service = EIREGISTERPARTY + self.send_vtn_request('oadrQueryRegistration', OadrQueryRegistrationBuilder().build()) + + def send_oadr_create_party_registration(self): + """Send oadrCreatePartyRegistration to the VTN.""" + _log.debug('VEN: oadrCreatePartyRegistration') + self.oadr_current_service = EIREGISTERPARTY + send_signature = (self.security_level == 'high') + # OADR rule 404: If the VEN hasn't registered before, venID and registrationID should be empty. + builder = OadrCreatePartyRegistrationBuilder(ven_id=None, xml_signature=send_signature, ven_name=self.ven_name) + self.send_vtn_request('oadrCreatePartyRegistration', builder.build()) + + def send_oadr_request_event(self): + """Send oadrRequestEvent to the VTN.""" + _log.debug('VEN: oadrRequestEvent') + self.oadr_current_service = EIEVENT + self.send_vtn_request('oadrRequestEvent', OadrRequestEventBuilder(ven_id=self.ven_id).build()) + + def send_oadr_created_event(self, event, error_code=None, error_message=None): + """ + Send oadrCreatedEvent to the VTN. + + @param event: (EiEvent) The event that is the subject of the request. + @param error_code: (string) eventResponse error code. Used when reporting event protocol errors. + @param error_message: (string) eventResponse error message. Used when reporting event protocol errors. + """ + _log.debug('VEN: oadrCreatedEvent') + self.oadr_current_service = EIEVENT + builder = OadrCreatedEventBuilder(event=event, ven_id=self.ven_id, + error_code=error_code, error_message=error_message) + self.send_vtn_request('oadrCreatedEvent', builder.build()) + + def send_oadr_register_report(self): + """ + Send oadrRegisterReport (METADATA) to the VTN. + + Sample oadrRegisterReport from the OpenADR Program Guide: + + + RegReq120615_122508_975 + + --- See oadr_report() --- + + ec27de207837e1048fd3 + + """ + _log.debug('VEN: oadrRegisterReport') + self.oadr_current_service = EIREPORT + # The VEN is currently hard-coded to support the 'telemetry' report, which sends baseline and measured power, + # and the 'telemetry_status' report, which sends online and manual_override status. + # In order to support additional reports and telemetry types, the VEN would need to store other data elements + # as additional columns in its SQLite database. + builder = OadrRegisterReportBuilder(reports=self.metadata_reports(), ven_id=self.ven_id) + # The EPRI VTN server responds to this request with "452: Invalid ID". Why? + self.send_vtn_request('oadrRegisterReport', builder.build()) + + def send_oadr_update_report(self, report): + """ + Send oadrUpdateReport to the VTN. + + Sample oadrUpdateReport from the OpenADR Program Guide: + + + ReportUpdReqID130615_192730_445 + + --- See OadrUpdateReportBuilder --- + + VEN130615_192312_582 + + + @param report: (EiReport) The report for which telemetry should be sent. + """ + _log.debug('VEN: oadrUpdateReport (report {})'.format(report.report_request_id)) + self.oadr_current_service = EIREPORT + telemetry = self.get_new_telemetry_for_report(report) if report.report_specifier_id == 'telemetry' else [] + builder = OadrUpdateReportBuilder(report=report, + telemetry=telemetry, + online=self.ven_online, + manual_override=self.ven_manual_override, + ven_id=self.ven_id) + self.send_vtn_request('oadrUpdateReport', builder.build()) + report.last_report = utils.get_aware_utc_now() + self.commit() + + def send_oadr_created_report(self, report_request): + """ + Send oadrCreatedReport to the VTN. + + @param report_request: (oadrReportRequestType) The VTN's report request. + """ + _log.debug('VEN: oadrCreatedReport') + self.oadr_current_service = EIREPORT + builder = OadrCreatedReportBuilder(report_request_id=report_request.reportRequestID, + ven_id=self.ven_id, + pending_report_request_ids=self.get_pending_report_request_ids()) + self.send_vtn_request('oadrCreatedReport', builder.build()) + + def send_oadr_canceled_report(self, report_request_id): + """ + Send oadrCanceledReport to the VTN. + + @param report_request_id: (string) The reportRequestID of the report that has been canceled. + """ + _log.debug('VEN: oadrCanceledReport') + self.oadr_current_service = EIREPORT + builder = OadrCanceledReportBuilder(request_id=self.oadr_current_request_id, + report_request_id=report_request_id, + ven_id=self.ven_id, + pending_report_request_ids=self.get_pending_report_request_ids()) + self.send_vtn_request('oadrCanceledReport', builder.build()) + + def send_oadr_response(self, response_description, response_code): + """ + Send an oadrResponse to the VTN. + + @param response_description: (string The response description. + @param response_code: (string) The response code, 200 if OK. + """ + _log.debug('VEN: oadrResponse') + builder = OadrResponseBuilder(response_code=response_code, + response_description=response_description, + request_id=self.oadr_current_request_id or '0', + ven_id=self.ven_id) + self.send_vtn_request('oadrResponse', builder.build()) + + def send_vtn_request(self, request_name, request_object): + """ + Send a request to the VTN. If the VTN returns a non-empty response, service that request. + + Wrap the request in a SignedObject and then in Payload XML, and post it to the VTN via HTTP. + If using high security, calculate a digital signature and include it in the request payload. + + @param request_name: (string) The name of the SignedObject attribute where the request is attached. + @param request_object: (various oadr object types) The request to send. + """ + signed_object = oadrSignedObject(**{request_name: request_object}) + try: + # Export the SignedObject as an XML string. + buff = io.StringIO() + signed_object.export(buff, 1, pretty_print=True) + signed_object_xml = buff.getvalue() + except Exception as err: + raise OpenADRInterfaceException('Error exporting the SignedObject: {}'.format(err), None) + + if self.security_level == 'high': + try: + signature_lxml, signed_object_lxml = self.calculate_signature(signed_object_xml) + except Exception as err: + raise OpenADRInterfaceException('Error signing the SignedObject: {}'.format(err), None) + payload_lxml = self.payload_element(signature_lxml, signed_object_lxml) + try: + # Verify that the payload, with signature, is well-formed and can be validated. + signxml.XMLVerifier().verify(payload_lxml, ca_pem_file=VTN_CA_CERT_FILENAME) + except Exception as err: + raise OpenADRInterfaceException('Error verifying the SignedObject: {}'.format(err), None) + else: + signed_object_lxml = etree_.fromstring(signed_object_xml) + payload_lxml = self.payload_element(None, signed_object_lxml) + + if self.log_xml: + _log.debug('VEN PAYLOAD:') + _log.debug('\n{}'.format(etree_.tostring(payload_lxml, pretty_print=True))) + + # Post payload XML to the VTN as an HTTP request. Return the VTN's response, if any. + endpoint = self.vtn_address + (self.oadr_current_service or POLL) + try: + payload_xml = etree_.tostring(payload_lxml) + # OADR rule 53: If simple HTTP mode is used, send the following headers: Host, Content-Length, Content-Type. + # The EPRI VTN server responds with a 400 "bad request" if a "Host" header is sent. + _log.debug('Posting VEN request to {}'.format(endpoint)) + response = requests.post(endpoint, data=payload_xml, headers={ + # "Host": endpoint, + "Content-Length": str(len(payload_xml)), + "Content-Type": "application/xml"}) + http_code = response.status_code + if http_code == 200: + if len(response.content) > 0: + self.core.spawn(self.service_vtn_request, response.content) + else: + _log.warning('Received zero-length request from VTN') + elif http_code == 204: + # Empty response received. Take no action. + _log.debug('Empty response received from {}'.format(endpoint)) + else: + _log.error('Error in http request to {}: response={}'.format(endpoint, http_code), exc_info=True) + raise OpenADRInterfaceException('Error in VTN request: {}'.format(http_code), None) + except ConnectionError: + _log.warning('ConnectionError in http request to {} (is the VTN offline?)'.format(endpoint)) + return None + except Exception as err: + raise OpenADRInterfaceException('Error posting OADR XML: {}'.format(err), None) + + # ***************** VOLTTRON RPCs ******************** + + @RPC.export + def respond_to_event(self, event_id, opt_in_choice=None): + """ + Respond to an event, opting in or opting out. + + If an event's status=unresponded, it is awaiting this call. + When this RPC is received, the VENAgent sends an eventResponse to + the VTN, indicating whether optIn or optOut has been chosen. + If an event remains unresponded for a set period of time, + it times out and automatically optsIn to the event. + + Since this call causes a change in the event's status, it triggers + a PubSub call for the event update, as described above. + + @param event_id: (String) ID of an event. + @param opt_in_choice: (String) 'OptIn' to opt into the event, anything else is treated as 'OptOut'. + """ + event = self.get_event_for_id(event_id) + if event: + if opt_in_choice == event.OPT_TYPE_OPT_IN: + event.opt_type = opt_in_choice + else: + event.opt_type = event.OPT_TYPE_OPT_OUT + self.commit() + _log.debug('RPC respond_to_event: Sending {} for event ID {}'.format(event.opt_type, event_id)) + self.send_oadr_created_event(event) + else: + raise OpenADRInterfaceException('No event found for event_id {}'.format(event_id), None) + + @RPC.export + def add_event_for_test(self, event_id, request_id, start_time): + """Add an event to the database and cache. Used during regression testing only.""" + _log.debug('RPC add_event_for_test: Creating event with ID {}'.format(event_id)) + event = EiEvent(event_id, request_id) + event.start_time = parser.parse(start_time) + self.add_event(event) + + @RPC.export + def get_events(self, **kwargs): + """ + Return a list of events as a JSON string. + + See _get_eievents() for a list of parameters and a description of method behavior. + + Sample request: + self.get_events(started_after=utils.get_aware_utc_now() - timedelta(hours=1), + end_time_before=utils.get_aware_utc_now()) + + @return: (JSON) A list of EiEvents -- see 'PubSub: event update'. + """ + _log.debug('RPC get_events') + events = self._get_events(**kwargs) + return None if events is None else self.json_object([e.as_json_compatible_object() for e in events]) + + @RPC.export + def get_telemetry_parameters(self): + """ + Return the VENAgent's current set of telemetry parameters. + + @return: (JSON) Current telemetry parameters -- see 'PubSub: telemetry parameters update'. + """ + _log.debug('RPC get_telemetry_parameters') + # If there is an active report, return its telemetry parameters. + # Otherwise return the telemetry report parameters in agent config. + rpts = self.active_reports() + report = rpts[0] if len(rpts) > 0 else self.metadata_report('telemetry') + # Extend what's reported to include parameters other than just telemetry parameters. + return {'online': self.ven_online, + 'manual_override': self.ven_manual_override, + 'telemetry': report.telemetry_parameters, + 'report parameters': self.json_object(report.as_json_compatible_object())} + + @RPC.export + def set_telemetry_status(self, online, manual_override): + """ + Update the VENAgent's reporting status. + + To be compliant with the OADR profile spec, set these properties to either 'TRUE' or 'FALSE'. + + @param online: (Boolean) Whether the VENAgent's resource is online. + @param manual_override: (Boolean) Whether resource control has been overridden. + """ + _log.debug('RPC set_telemetry_status: online={}, manual_override={}'.format(online, manual_override)) + # OADR rule 510: Provide a TELEMETRY_STATUS report that includes oadrOnline and oadrManualOverride values. + self.ven_online = online + self.ven_manual_override = manual_override + + @RPC.export + def report_telemetry(self, telemetry): + """ + Receive an update of the VENAgent's report metrics, and store them in the agent's database. + + Examples of telemetry are: + { + 'baseline_power_kw': '15.2', + 'current_power_kw': '371.1', + 'start_time': '2017-11-21T23:41:46.051405', + 'end_time': '2017-11-21T23:42:45.951405' + } + + @param telemetry: (JSON) Current value of each report metric, with reporting-interval start/end timestamps. + """ + _log.debug('RPC report_telemetry: {}'.format(telemetry)) + baseline_power_kw = telemetry.get('baseline_power_kw') + current_power_kw = telemetry.get('current_power_kw') + start_time = utils.parse_timestamp_string(telemetry.get('start_time')) + end_time = utils.parse_timestamp_string(telemetry.get('end_time')) + for report in self.active_reports(): + self.add_telemetry(EiTelemetryValues(report_request_id=report.report_request_id, + baseline_power_kw=baseline_power_kw, + current_power_kw=current_power_kw, + start_time=start_time, + end_time=end_time)) + + # ***************** VOLTTRON Pub/Sub Requests ******************** + + def publish_event(self, an_event): + """ + Publish an event. + + When an event is created/updated, it is published to the VOLTTRON bus + with a topic that includes 'openadr/event_update'. + + Event JSON structure: + { + "event_id" : String, + "creation_time" : DateTime, + "start_time" : DateTime, + "end_time" : DateTime or None, + "priority" : Integer, # Values: 0, 1, 2, 3. Usually expected to be 1. + "signals" : String, # Values: json string describing one or more signals. + "status" : String, # Values: unresponded, far, near, active, + # completed, canceled. + "opt_type" : String # Values: optIn, optOut, none. + } + + If an event status is 'unresponded', the VEN agent is awaiting a decision on + whether to optIn or optOut. The downstream agent that subscribes to this PubSub + message should communicate that choice to the VEN agent by calling respond_to_event() + (see below). The VEN agent then relays the choice to the VTN. + + @param an_event: an EiEvent. + """ + if an_event.test_event != 'false': + # OADR rule 6: If testEvent is present and != "false", handle the event as a test event. + _log.debug('Suppressing publication of test event {}'.format(an_event)) + else: + _log.debug('Publishing event {}'.format(an_event)) + request_headers = {headers.TIMESTAMP: format_timestamp(utils.get_aware_utc_now())} + self.vip.pubsub.publish(peer='pubsub', + topic=topics.OPENADR_EVENT+'/'+self.ven_id, + headers=request_headers, + message=self.json_object(an_event.as_json_compatible_object())) + + def publish_telemetry_parameters_for_report(self, report): + """ + Publish telemetry parameters. + + When the VEN agent telemetry reporting parameters have been updated (by the VTN), + they are published with a topic that includes 'openadr/telemetry_parameters'. + If a particular report has been updated, the reported parameters are for that report. + + Telemetry parameters JSON example: + { + "telemetry": { + "baseline_power_kw": { + "r_id": "baseline_power", + "min_frequency": "30", + "max_frequency": "60", + "report_type": "baseline", + "reading_type": "Direct Read", + "units": "powerReal", + "method_name": "get_baseline_power" + } + "current_power_kw": { + "r_id": "actual_power", + "min_frequency": "30", + "max_frequency": "60", + "report_type": "reading", + "reading_type": "Direct Read", + "units": "powerReal", + "method_name": "get_current_power" + } + "manual_override": "False", + "report_status": "active", + "online": "False", + } + } + + The above example indicates that, for reporting purposes, telemetry values + for baseline_power and actual_power should be updated -- via report_telemetry() -- at + least once every 30 seconds. + + Telemetry value definitions such as baseline_power and actual_power come from the + agent configuration. + + @param report: (EiReport) The report whose parameters should be published. + """ + _log.debug('Publishing telemetry parameters') + request_headers = {headers.TIMESTAMP: format_timestamp(utils.get_aware_utc_now())} + self.vip.pubsub.publish(peer='pubsub', + topic=topics.OPENADR_STATUS+'/'+self.ven_id, + headers=request_headers, + message=report.telemetry_parameters) + + # ***************** Database Requests ******************** + + def active_events(self): + """Return a list of events that are neither COMPLETED nor CANCELED.""" + return self._get_events() + + def get_event_for_id(self, event_id): + """Return the event with ID event_id, or None if not found.""" + event_list = self._get_events(event_id=event_id, in_progress_only=False) + return event_list[0] if len(event_list) == 1 else None + + def _get_events(self, event_id=None, in_progress_only=True, started_after=None, end_time_before=None): + """ + Return a list of EiEvents. (internal method) + + By default, return only event requests with status=active or status=unresponded. + + If an event's status=active, a DR event is currently in progress. + + @param event_id: (String) Default None. + @param in_progress_only: (Boolean) Default True. + @param started_after: (DateTime) Default None. + @param end_time_before: (DateTime) Default None. + @return: A list of EiEvents. + """ + # For requests by event ID, query the cache first before querying the database. + if event_id: + event = self._active_events.get(event_id, None) + if event: + return [event] + + db_event = globals()['EiEvent'] + events = self.get_db_session().query(db_event) + if event_id is not None: + events = events.filter(db_event.event_id == event_id) + if in_progress_only: + events = events.filter(~db_event.status.in_([EiEvent.STATUS_COMPLETED, EiEvent.STATUS_CANCELED])) + if started_after: + events = events.filter(db_event.start_time > started_after) + if end_time_before and db_event.end_time: + # An event's end_time can be None, indicating that it doesn't expire until Canceled. + # If the event's end_time is None, don't apply this filter to it. + events = events.filter(db_event.end_time < end_time_before) + return events.all() + + def add_event(self, event): + """A new event has been created. Add it to the event cache, and also to the database.""" + self._active_events[event.event_id] = event + self.get_db_session().add(event) + self.commit() + + def set_event_status(self, event, status): + _log.debug('Transitioning status to {} for event ID {}'.format(status, event.event_id)) + event.status = status + self.commit() + + def expire_event(self, event): + """Remove the event from the event cache. (It remains in the SQLite database.)""" + self._active_events.pop(event.event_id) + + def active_reports(self): + """Return a list of reports that are neither COMPLETED nor CANCELED.""" + return self._get_reports() + + def add_report(self, report): + """A new report has been created. Add it to the report cache, and also to the database.""" + self._active_reports[report.report_request_id] = report + self.get_db_session().add(report) + self.commit() + + def set_report_status(self, report, status): + _log.debug('Transitioning status to {} for report request ID {}'.format(status, report.report_request_id)) + report.status = status + self.commit() + + def expire_report(self, report): + """Remove the report from the report cache. (It remains in the SQLite database.)""" + self._active_reports.pop(report.report_request_id) + + def get_report_for_report_request_id(self, report_request_id): + """Return the EiReport with request ID report_request_id, or None if not found.""" + report_list = self._get_reports(report_request_id=report_request_id, active_only=False) + return report_list[0] if len(report_list) == 1 else None + + def get_reports_for_report_specifier_id(self, report_specifier_id): + """Return the EiReport with request ID report_request_id, or None if not found.""" + return self._get_reports(report_specifier_id=report_specifier_id, active_only=True) + + def get_pending_report_request_ids(self): + """Return a list of reportRequestIDs for each active report.""" + # OpenADR rule 329: Include all current report request IDs in the oadrPendingReports list. + return [r.report_request_id for r in self._get_reports()] + + def _get_reports(self, report_request_id=None, report_specifier_id=None, active_only=True, + started_after=None, end_time_before=None): + """ + Return a list of EiReport. + + By default, return only report requests with status=active. + + @param report_request_id: (String) Default None. + @param report_specifier_id: (String) Default None. + @param active_only: (Boolean) Default True. + @param started_after: (DateTime) Default None. + @param end_time_before: (DateTime) Default None. + @return: A list of EiReports. + """ + # For requests by report ID, query the cache first before querying the database. + if report_request_id: + report = self._active_reports.get(report_request_id, None) + if report: + return [report] + + db_report = globals()['EiReport'] + reports = self.get_db_session().query(db_report) + if report_request_id is not None: + reports = reports.filter(db_report.report_request_id == report_request_id) + if report_specifier_id is not None: + reports = reports.filter(db_report.report_specifier_id == report_specifier_id) + if active_only: + reports = reports.filter(~db_report.status.in_([EiReport.STATUS_COMPLETED, EiReport.STATUS_CANCELED])) + if started_after: + reports = reports.filter(db_report.start_time > started_after) + if end_time_before and db_report.end_time: + # A report's end_time can be None, indicating that it doesn't expire until Canceled. + # If the report's end_time is None, don't apply this filter to it. + reports = reports.filter(db_report.end_time < end_time_before) + return reports.all() + + def metadata_reports(self): + """Return an EiReport instance containing telemetry metadata for each report definition in agent config.""" + return [self.metadata_report(rpt_name) for rpt_name in self.report_parameters.keys()] + + def metadata_report(self, specifier_id): + """Return an EiReport instance for the indicated specifier_id, or None if its' not in agent config.""" + params = self.report_parameters.get(specifier_id, None) + report = EiReport('', '', specifier_id) # No requestID, no reportRequestID + report.name = params.get('report_name_metadata', None) + try: + interval_secs = int(params.get('report_interval_secs_default', None)) + except ValueError: + error_msg = 'Default report interval {} is not an integer number of seconds'.format(default) + raise OpenADRInternalException(error_msg, OADR_BAD_DATA) + report.interval_secs = interval_secs + report.telemetry_parameters = jsonapi.dumps(params.get('telemetry_parameters', None)) + report.report_specifier_id = specifier_id + report.status = report.STATUS_INACTIVE + return report + + def get_new_telemetry_for_report(self, report): + """Query for relevant telemetry that's arrived since the report was last sent to the VTN.""" + db_telemetry_values = globals()['EiTelemetryValues'] + telemetry = self.get_db_session().query(db_telemetry_values) + telemetry = telemetry.filter(db_telemetry_values.report_request_id == report.report_request_id) + telemetry = telemetry.filter(db_telemetry_values.created_on > report.last_report) + return telemetry.all() + + def add_telemetry(self, telemetry): + """New telemetry has been received. Add it to the database.""" + self.get_db_session().add(telemetry) + self.commit() + + def telemetry_cleanup(self): + """gevent thread for periodically deleting week-old telemetry from the database.""" + db_telemetry_values = globals()['EiTelemetryValues'] + telemetry = self.get_db_session().query(db_telemetry_values) + total_rows = telemetry.count() + telemetry = telemetry.filter(db_telemetry_values.created_on < utils.get_aware_utc_now() - timedelta(days=7)) + deleted_row_count = telemetry.delete() + if deleted_row_count: + _log.debug('Deleting {} outdated of {} total telemetry rows in db'.format(deleted_row_count, total_rows)) + self.commit() + + def commit(self): + """Flush any modified objects to the SQLite database.""" + self.get_db_session().commit() + + def get_db_session(self): + """Return the SQLite database session. Initialize the session if this is the first time in.""" + if not self._db_session: + # First time: create a SQLAlchemy engine and session. + try: + database_dir = os.path.dirname(self.db_path) + if not os.path.exists(database_dir): + _log.debug('Creating sqlite database directory {}'.format(database_dir)) + os.makedirs(database_dir) + engine_path = 'sqlite:///' + self.db_path + _log.debug('Connecting to sqlite database {}'.format(engine_path)) + engine = create_engine(engine_path).connect() + ORMBase.metadata.create_all(engine) + self._db_session = sessionmaker(bind=engine)() + except AttributeError as err: + error_msg = 'Unable to open sqlite database named {}: {}'.format(self.db_path, err) + raise OpenADRInterfaceException(error_msg, None) + return self._db_session + + # ***************** Utility Methods ******************** + + @staticmethod + def payload_element(signature_lxml, signed_object_lxml): + """ + Construct and return an XML element for Payload. + + Append a child Signature element if one is provided. + Append a child SignedObject element. + + @param signature_lxml: (Element or None) Signature element. + @param signed_object_lxml: (Element) SignedObject element. + @return: (Element) Payload element. + """ + payload = etree_.Element("{http://openadr.org/oadr-2.0b/2012/07}oadrPayload", + nsmap=signed_object_lxml.nsmap) + if signature_lxml: + payload.append(signature_lxml) + payload.append(signed_object_lxml) + return payload + + @staticmethod + def calculate_signature(signed_object_xml): + """ + Calculate a digital signature for the SignedObject to be sent to the VTN. + + @param signed_object_xml: (xml string) A SignedObject. + @return: (lxml) A Signature and a SignedObject. + """ + signed_object_lxml = etree_.fromstring(signed_object_xml) + signed_object_lxml.set('Id', 'signedObject') + # Use XMLSigner to create a Signature. + # Use "detached method": the signature lives alonside the signed object in the XML element tree. + # Use c14n "exclusive canonicalization": the signature is independent of namespace inclusion/exclusion. + signer = signxml.XMLSigner(method=signxml.methods.detached, + c14n_algorithm='http://www.w3.org/2001/10/xml-exc-c14n#') + signature_lxml = signer.sign(signed_object_lxml, + key=open(KEY_FILENAME, 'rb').read(), + cert=open(CERT_FILENAME, 'rb').read(), + key_name='123') + # This generated Signature lacks the ReplayProtect property described in OpenADR profile spec section 10.6.3. + return signature_lxml, signed_object_lxml + + def register_endpoints(self): + """ + Register each endpoint URL and its callback. + + These endpoint definitions are used only by "PUSH" style VTN communications, + not by responses to VEN polls. + """ + _log.debug("Registering Endpoints: {}".format(self.__class__.__name__)) + for endpoint in OPENADR_ENDPOINTS.values(): + self.vip.web.register_endpoint(endpoint.url, getattr(self, endpoint.callback), "raw") + + def json_object(self, obj): + """Ensure that an object is valid JSON by dumping it with json_converter and then reloading it.""" + obj_string = jsonapi.dumps(obj, default=self.json_converter) + obj_json = jsonapi.loads(obj_string) + return obj_json + + @staticmethod + def json_converter(object_to_dump): + """When calling jsonapi.dumps, convert datetime instances to strings.""" + if isinstance(object_to_dump, dt): + return object_to_dump.__str__() + + +def main(): + """Start the agent.""" + utils.vip_main(ven_agent, identity='venagent', version=__version__) + + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + pass diff --git a/services/unsupported/OpenADRVenAgent/openadrven/models.py b/services/unsupported/OpenADRVenAgent/openadrven/models.py new file mode 100644 index 0000000000..30a3bb4963 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/models.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from datetime import timedelta +from dateutil import parser + +from sqlalchemy import Column, String, Integer, DateTime, Float +from sqlalchemy.ext.declarative import declarative_base + +from volttron.platform.agent import utils + +ORMBase = declarative_base() + + +class EiEvent(ORMBase): + """ + Model object for an event. + + This model object, and the other models in this module, are managed by the SQLAlchemy ORM, + and are persisted in a SQLite database. + + Note that the timestamps are stored as ISO8601 strings, by SQLite convention. + Timezone awareness is retained when they are persisted. + They're stored as strings -- iso_start_time, etc. -- but agent code uses property + methods -- start_time(), etc. -- to get and set them as datetime objects. + """ + + __tablename__ = 'EiEvent' + + rowid = Column(Integer, primary_key=True, autoincrement=True) + event_id = Column(String, nullable=False) + request_id = Column(String, nullable=False) + creation_time = Column(DateTime, nullable=False) + iso_start_time = Column(String, nullable=False) # ISO 8601 timestamp in UTC + iso_end_time = Column(String, nullable=True) # ISO 8601 timestamp in UTC + signals = Column(String, nullable=False) + status = Column(String, nullable=False) + opt_type = Column(String, nullable=False) + priority = Column(Integer, nullable=False) + modification_number = Column(Integer, nullable=False) + test_event = Column(String, nullable=False, default='false') + iso_dtstart = Column(String, nullable=True) # ISO 8601 timestamp in UTC + duration = Column(String, nullable=True) # ISO 8601 duration string + start_after = Column(String, nullable=True, default='') # ISO 8601 duration string + + attribute_names = ['event_id', 'creation_time', 'start_time', 'end_time', 'priority', + 'signals', 'status', 'opt_type'] + + STATUS_UNRESPONDED = 'unresponded' + STATUS_FAR = 'far' + STATUS_NEAR = 'near' + STATUS_ACTIVE = 'active' + STATUS_COMPLETED = 'completed' + STATUS_CANCELED = 'cancelled' + STATUS_VALUES = [STATUS_UNRESPONDED, STATUS_FAR, STATUS_NEAR, STATUS_ACTIVE, STATUS_COMPLETED, + STATUS_CANCELED] + + OPT_TYPE_OPT_IN = 'optIn' + OPT_TYPE_OPT_OUT = 'optOut' + OPT_TYPE_NONE = 'none' + + def __init__(self, request_id, event_id): + self.request_id = request_id + self.creation_time = utils.get_aware_utc_now() + self.event_id = event_id + self.signals = '' + self.status = self.STATUS_UNRESPONDED + self.opt_type = self.OPT_TYPE_NONE + self.priority = 1 + self.modification_number = 0 # Incremented by the VTN if/when the event changes + self.test_event = 'false' + + def __str__(self): + """Format the instance as a string suitable for trace display.""" + my_str = '{}: '.format(self.__class__.__name__) + my_str += 'event_id:{}; '.format(self.event_id) + my_str += 'start_time:{}; '.format(self.start_time) + my_str += 'end_time:{}; '.format(self.end_time) + my_str += 'opt_type:{}; '.format(self.opt_type) + my_str += 'event_id:{}; '.format(self.event_id) + my_str += 'status:{}; '.format(self.status) + my_str += 'priority:{}; '.format(self.priority) + my_str += 'modification_number:{}; '.format(self.modification_number) + my_str += 'signals:{}; '.format(self.signals) + return my_str + + @property + def start_time(self): + return parser.parse(self.iso_start_time) if self.iso_start_time else None + + @property + def end_time(self): + return parser.parse(self.iso_end_time) if self.iso_end_time else None + + @property + def dtstart(self): + return parser.parse(self.iso_dtstart) if self.iso_dtstart else None + + @start_time.setter + def start_time(self, t): + self.iso_start_time = utils.format_timestamp(t) if t else None + + @end_time.setter + def end_time(self, t): + self.iso_end_time = utils.format_timestamp(t) if t else None + + @dtstart.setter + def dtstart(self, t): + self.iso_dtstart = utils.format_timestamp(t) if t else None + + @classmethod + def sample_event(cls): + """Return a sample EiEvent for debugging purposes.""" + sample = cls('123456', '12345') + sample.start_time = utils.get_aware_utc_now() + sample.end_time = sample.start_time + timedelta(hours=1) + sample.opt_type = 'optIn' + return sample + + def is_active(self): + return self.status not in [self.STATUS_COMPLETED, self.STATUS_CANCELED] + + def as_json_compatible_object(self): + """Format the object as JSON that will be returned in response to an RPC, or sent in a pub/sub.""" + return {attname: getattr(self, attname) for attname in self.attribute_names} + + def copy_from_event(self, another_event): + # Do not copy creation_time from another_event + self.event_id = another_event.event_id + self.request_id = another_event.request_id + self.start_time = another_event.start_time + self.end_time = another_event.end_time + self.priority = another_event.priority + self.signals = another_event.signals + # Do not copy status from another_event + # Do not copy opt_type from another_event + self.modification_number = another_event.modification_number + self.test_event = another_event.test_event + self.dtstart = another_event.dtstart + self.duration = another_event.duration + self.start_after = another_event.start_after + + +class EiReport(ORMBase): + """Model object for a report.""" + + __tablename__ = 'EiReport' + + STATUS_INACTIVE = 'inactive' + STATUS_ACTIVE = 'active' + STATUS_COMPLETED = 'completed' + STATUS_CANCELED = 'cancelled' + + rowid = Column(Integer, primary_key=True) + created_on = Column(DateTime) + request_id = Column(String) + report_request_id = Column(String) + report_specifier_id = Column(String) + iso_start_time = Column(String, nullable=False) # ISO 8601 timestamp in UTC + iso_end_time = Column(String, nullable=True) # ISO 8601 timestamp in UTC + duration = Column(String) # ISO 8601 duration + status = Column(String) + iso_last_report = Column(String) # ISO 8601 timestamp in UTC + name = Column(String) + interval_secs = Column(Integer) + granularity_secs = Column(String) + telemetry_parameters = Column(String) + + attribute_names = ["status", "report_specifier_id", "report_request_id", "request_id", + "interval_secs", "granularity_secs", + "start_time", "end_time", "last_report", "created_on"] + + def __init__(self, request_id, report_request_id, report_specifier_id): + self.created_on = utils.get_aware_utc_now() + self.request_id = request_id + self.report_request_id = report_request_id + self.report_specifier_id = report_specifier_id + self.status = 'inactive' + self.last_report = utils.get_aware_utc_now() + + def __str__(self): + """Format the instance as a string suitable for trace display.""" + my_str = '{}: '.format(self.__class__.__name__) + my_str += 'report_request_id:{}; '.format(self.report_request_id) + my_str += 'report_specifier_id:{}; '.format(self.report_specifier_id) + my_str += 'start_time:{}; '.format(self.start_time) + my_str += 'end_time:{}; '.format(self.end_time) + my_str += 'status:{}; '.format(self.status) + return my_str + + @property + def start_time(self): + return parser.parse(self.iso_start_time) if self.iso_start_time else None + + @property + def end_time(self): + return parser.parse(self.iso_end_time) if self.iso_end_time else None + + @property + def last_report(self): + return parser.parse(self.iso_last_report) if self.iso_last_report else None + + @start_time.setter + def start_time(self, t): + self.iso_start_time = utils.format_timestamp(t) if t else None + + @end_time.setter + def end_time(self, t): + self.iso_end_time = utils.format_timestamp(t) if t else None + + @last_report.setter + def last_report(self, t): + self.iso_last_report = utils.format_timestamp(t) if t else None + + def is_active(self): + return self.status not in [self.STATUS_COMPLETED, self.STATUS_CANCELED] + + def as_json_compatible_object(self): + """Format the object as JSON that will be returned in response to an RPC, or sent in a pub/sub.""" + return {attname: getattr(self, attname) for attname in self.attribute_names} + + def copy_from_report(self, another_report): + """(Selectively) Copy the contents of another_report to this one.""" + self.request_id = another_report.request_id + self.report_request_id = another_report.report_request_id + self.report_specifier_id = another_report.report_specifier_id + self.start_time = another_report.start_time + self.end_time = another_report.end_time + self.duration = another_report.duration + self.granularity_secs = another_report.granularity_secs + # Do not copy created_on from another_report + # Do not copy status from another_report + # Do not copy last_report from another_report + self.name = another_report.name + self.interval_secs = another_report.interval_secs + self.telemetry_parameters = another_report.telemetry_parameters + + +class EiTelemetryValues(ORMBase): + """Model object for telemetry values.""" + + __tablename__ = 'EiTelemetryValues' + + rowid = Column(Integer, primary_key=True) + created_on = Column(DateTime) + report_request_id = Column(String) + baseline_power_kw = Column(Float) + current_power_kw = Column(Float) + iso_start_time = Column(String) # ISO 8601 timestamp in UTC + iso_end_time = Column(String) # ISO 8601 timestamp in UTC + + attribute_names = ['created_on', 'report_request_id', 'baseline_power_kw', 'current_power_kw', + 'start_time', 'end_time'] + + def __init__(self, report_request_id=None, + baseline_power_kw=None, current_power_kw=None, + start_time=None, end_time=None): + self.created_on = utils.get_aware_utc_now() + self.report_request_id = report_request_id + self.baseline_power_kw = baseline_power_kw + self.current_power_kw = current_power_kw + self.start_time = start_time + self.end_time = end_time + + def __str__(self): + """Format the instance as a string suitable for trace display.""" + my_str = '{}: '.format(self.__class__.__name__) + my_str += 'created_on:{}; '.format(self.created_on) + my_str += 'report_request_id:{}; '.format(self.report_request_id) + my_str += 'baseline_power_kw:{}; '.format(self.baseline_power_kw) + my_str += 'current_power_kw:{} '.format(self.current_power_kw) + my_str += 'start_time:{} '.format(self.start_time) + my_str += 'end_time:{} '.format(self.end_time) + return my_str + + @property + def start_time(self): + return parser.parse(self.iso_start_time) if self.iso_start_time else None + + @property + def end_time(self): + return parser.parse(self.iso_end_time) if self.iso_end_time else None + + @start_time.setter + def start_time(self, t): + self.iso_start_time = utils.format_timestamp(t) if t else None + + @end_time.setter + def end_time(self, t): + self.iso_end_time = utils.format_timestamp(t) if t else None + + @classmethod + def sample_values(cls): + """Return a sample set of telemetry values for debugging purposes.""" + telemetry_values = cls() + telemetry_values.report_request_id = '123' + telemetry_values.baseline_power_kw = 37.1 + telemetry_values.current_power_kw = 272.3 + return telemetry_values + + def as_json_compatible_object(self): + """Format the object as JSON that will be returned in response to an RPC, or sent in a pub/sub.""" + return {attname: getattr(self, attname) for attname in self.attribute_names} + + def get_baseline_power(self): + return self.baseline_power_kw + + def get_current_power(self): + return self.current_power_kw + + def get_duration(self): + return self.end_time - self.start_time diff --git a/services/unsupported/OpenADRVenAgent/openadrven/oadr_20b.py b/services/unsupported/OpenADRVenAgent/openadrven/oadr_20b.py new file mode 100644 index 0000000000..3529bc1441 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/oadr_20b.py @@ -0,0 +1,22878 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Generated Fri Feb 9 14:26:18 2018 by generateDS.py version 2.29.6. +# Python 2.7.14 (default, Sep 27 2017, 12:15:00) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] +# +# Command line options: +# ('-o', 'oadr_20b.py') +# +# Command line arguments: +# oadr_20b.xsd +# +# Command line: +# /Users/robcalvert/repos/volttron/env/bin/generateds -o "oadr_20b.py" oadr_20b.xsd +# +# Current working directory (os.getcwd()): +# oadr20b_schema +# + +import sys +import re as re_ +import base64 +import datetime as datetime_ +import warnings as warnings_ +try: + from lxml import etree as etree_ +except ImportError: + from xml.etree import ElementTree as etree_ + + +Validate_simpletypes_ = True +BaseStrType_ = str + + +def parsexml_(infile, parser=None, **kwargs): + if parser is None: + # Use the lxml ElementTree compatible parser so that, e.g., + # we ignore comments. + try: + parser = etree_.ETCompatXMLParser() + except AttributeError: + # fallback to xml.etree + parser = etree_.XMLParser() + doc = etree_.parse(infile, parser=parser, **kwargs) + return doc + +def parsexmlstring_(instring, parser=None, **kwargs): + if parser is None: + # Use the lxml ElementTree compatible parser so that, e.g., + # we ignore comments. + try: + parser = etree_.ETCompatXMLParser() + except AttributeError: + # fallback to xml.etree + parser = etree_.XMLParser() + element = etree_.fromstring(instring, parser=parser, **kwargs) + return element + +# +# Namespace prefix definition table (and other attributes, too) +# +# The module generatedsnamespaces, if it is importable, must contain +# a dictionary named GeneratedsNamespaceDefs. This Python dictionary +# should map element type names (strings) to XML schema namespace prefix +# definitions. The export method for any class for which there is +# a namespace prefix definition, will export that definition in the +# XML representation of that element. See the export method of +# any generated element type class for a example of the use of this +# table. +# A sample table is: +# +# # File: generatedsnamespaces.py +# +# GenerateDSNamespaceDefs = { +# "ElementtypeA": "http://www.xxx.com/namespaceA", +# "ElementtypeB": "http://www.xxx.com/namespaceB", +# } +# + +try: + from generatedsnamespaces import GenerateDSNamespaceDefs as GenerateDSNamespaceDefs_ +except ImportError: + GenerateDSNamespaceDefs_ = {} + +# +# The root super-class for element type classes +# +# Calls to the methods in these classes are generated by generateDS.py. +# You can replace these methods by re-implementing the following class +# in a module named generatedssuper.py. + +try: + from generatedssuper import GeneratedsSuper +except ImportError as exp: + + class GeneratedsSuper(object): + tzoff_pattern = re_.compile(r'(\+|-)((0\d|1[0-3]):[0-5]\d|14:00)$') + class _FixedOffsetTZ(datetime_.tzinfo): + def __init__(self, offset, name): + self.__offset = datetime_.timedelta(minutes=offset) + self.__name = name + def utcoffset(self, dt): + return self.__offset + def tzname(self, dt): + return self.__name + def dst(self, dt): + return None + def gds_format_string(self, input_data, input_name=''): + return input_data + def gds_validate_string(self, input_data, node=None, input_name=''): + if not input_data: + return '' + else: + return input_data + def gds_format_base64(self, input_data, input_name=''): + return base64.b64encode(input_data) + def gds_validate_base64(self, input_data, node=None, input_name=''): + return input_data + def gds_format_integer(self, input_data, input_name=''): + return '%d' % input_data + def gds_validate_integer(self, input_data, node=None, input_name=''): + return input_data + def gds_format_integer_list(self, input_data, input_name=''): + return '%s' % ' '.join(input_data) + def gds_validate_integer_list( + self, input_data, node=None, input_name=''): + values = input_data.split() + for value in values: + try: + int(value) + except (TypeError, ValueError): + raise_parse_error(node, 'Requires sequence of integers') + return values + def gds_format_float(self, input_data, input_name=''): + return ('%.15f' % input_data).rstrip('0') + def gds_validate_float(self, input_data, node=None, input_name=''): + return input_data + def gds_format_float_list(self, input_data, input_name=''): + return '%s' % ' '.join(input_data) + def gds_validate_float_list( + self, input_data, node=None, input_name=''): + values = input_data.split() + for value in values: + try: + float(value) + except (TypeError, ValueError): + raise_parse_error(node, 'Requires sequence of floats') + return values + def gds_format_double(self, input_data, input_name=''): + return '%e' % input_data + def gds_validate_double(self, input_data, node=None, input_name=''): + return input_data + def gds_format_double_list(self, input_data, input_name=''): + return '%s' % ' '.join(input_data) + def gds_validate_double_list( + self, input_data, node=None, input_name=''): + values = input_data.split() + for value in values: + try: + float(value) + except (TypeError, ValueError): + raise_parse_error(node, 'Requires sequence of doubles') + return values + def gds_format_boolean(self, input_data, input_name=''): + return ('%s' % input_data).lower() + def gds_validate_boolean(self, input_data, node=None, input_name=''): + return input_data + def gds_format_boolean_list(self, input_data, input_name=''): + return '%s' % ' '.join(input_data) + def gds_validate_boolean_list( + self, input_data, node=None, input_name=''): + values = input_data.split() + for value in values: + if value not in ('true', '1', 'false', '0', ): + raise_parse_error( + node, + 'Requires sequence of booleans ' + '("true", "1", "false", "0")') + return values + def gds_validate_datetime(self, input_data, node=None, input_name=''): + return input_data + def gds_format_datetime(self, input_data, input_name=''): + if input_data.microsecond == 0: + _svalue = '%04d-%02d-%02dT%02d:%02d:%02d' % ( + input_data.year, + input_data.month, + input_data.day, + input_data.hour, + input_data.minute, + input_data.second, + ) + else: + _svalue = '%04d-%02d-%02dT%02d:%02d:%02d.%s' % ( + input_data.year, + input_data.month, + input_data.day, + input_data.hour, + input_data.minute, + input_data.second, + ('%f' % (float(input_data.microsecond) / 1000000))[2:], + ) + if input_data.tzinfo is not None: + tzoff = input_data.tzinfo.utcoffset(input_data) + if tzoff is not None: + total_seconds = tzoff.seconds + (86400 * tzoff.days) + if total_seconds == 0: + _svalue += 'Z' + else: + if total_seconds < 0: + _svalue += '-' + total_seconds *= -1 + else: + _svalue += '+' + hours = total_seconds // 3600 + minutes = (total_seconds - (hours * 3600)) // 60 + _svalue += '{0:02d}:{1:02d}'.format(hours, minutes) + return _svalue + @classmethod + def gds_parse_datetime(cls, input_data): + tz = None + if input_data[-1] == 'Z': + tz = GeneratedsSuper._FixedOffsetTZ(0, 'UTC') + input_data = input_data[:-1] + else: + results = GeneratedsSuper.tzoff_pattern.search(input_data) + if results is not None: + tzoff_parts = results.group(2).split(':') + tzoff = int(tzoff_parts[0]) * 60 + int(tzoff_parts[1]) + if results.group(1) == '-': + tzoff *= -1 + tz = GeneratedsSuper._FixedOffsetTZ( + tzoff, results.group(0)) + input_data = input_data[:-6] + time_parts = input_data.split('.') + if len(time_parts) > 1: + micro_seconds = int(float('0.' + time_parts[1]) * 1000000) + input_data = '%s.%s' % (time_parts[0], micro_seconds, ) + dt = datetime_.datetime.strptime( + input_data, '%Y-%m-%dT%H:%M:%S.%f') + else: + dt = datetime_.datetime.strptime( + input_data, '%Y-%m-%dT%H:%M:%S') + dt = dt.replace(tzinfo=tz) + return dt + def gds_validate_date(self, input_data, node=None, input_name=''): + return input_data + def gds_format_date(self, input_data, input_name=''): + _svalue = '%04d-%02d-%02d' % ( + input_data.year, + input_data.month, + input_data.day, + ) + try: + if input_data.tzinfo is not None: + tzoff = input_data.tzinfo.utcoffset(input_data) + if tzoff is not None: + total_seconds = tzoff.seconds + (86400 * tzoff.days) + if total_seconds == 0: + _svalue += 'Z' + else: + if total_seconds < 0: + _svalue += '-' + total_seconds *= -1 + else: + _svalue += '+' + hours = total_seconds // 3600 + minutes = (total_seconds - (hours * 3600)) // 60 + _svalue += '{0:02d}:{1:02d}'.format( + hours, minutes) + except AttributeError: + pass + return _svalue + @classmethod + def gds_parse_date(cls, input_data): + tz = None + if input_data[-1] == 'Z': + tz = GeneratedsSuper._FixedOffsetTZ(0, 'UTC') + input_data = input_data[:-1] + else: + results = GeneratedsSuper.tzoff_pattern.search(input_data) + if results is not None: + tzoff_parts = results.group(2).split(':') + tzoff = int(tzoff_parts[0]) * 60 + int(tzoff_parts[1]) + if results.group(1) == '-': + tzoff *= -1 + tz = GeneratedsSuper._FixedOffsetTZ( + tzoff, results.group(0)) + input_data = input_data[:-6] + dt = datetime_.datetime.strptime(input_data, '%Y-%m-%d') + dt = dt.replace(tzinfo=tz) + return dt.date() + def gds_validate_time(self, input_data, node=None, input_name=''): + return input_data + def gds_format_time(self, input_data, input_name=''): + if input_data.microsecond == 0: + _svalue = '%02d:%02d:%02d' % ( + input_data.hour, + input_data.minute, + input_data.second, + ) + else: + _svalue = '%02d:%02d:%02d.%s' % ( + input_data.hour, + input_data.minute, + input_data.second, + ('%f' % (float(input_data.microsecond) / 1000000))[2:], + ) + if input_data.tzinfo is not None: + tzoff = input_data.tzinfo.utcoffset(input_data) + if tzoff is not None: + total_seconds = tzoff.seconds + (86400 * tzoff.days) + if total_seconds == 0: + _svalue += 'Z' + else: + if total_seconds < 0: + _svalue += '-' + total_seconds *= -1 + else: + _svalue += '+' + hours = total_seconds // 3600 + minutes = (total_seconds - (hours * 3600)) // 60 + _svalue += '{0:02d}:{1:02d}'.format(hours, minutes) + return _svalue + def gds_validate_simple_patterns(self, patterns, target): + # pat is a list of lists of strings/patterns. We should: + # - AND the outer elements + # - OR the inner elements + found1 = True + for patterns1 in patterns: + found2 = False + for patterns2 in patterns1: + if re_.search(patterns2, target) is not None: + found2 = True + break + if not found2: + found1 = False + break + return found1 + @classmethod + def gds_parse_time(cls, input_data): + tz = None + if input_data[-1] == 'Z': + tz = GeneratedsSuper._FixedOffsetTZ(0, 'UTC') + input_data = input_data[:-1] + else: + results = GeneratedsSuper.tzoff_pattern.search(input_data) + if results is not None: + tzoff_parts = results.group(2).split(':') + tzoff = int(tzoff_parts[0]) * 60 + int(tzoff_parts[1]) + if results.group(1) == '-': + tzoff *= -1 + tz = GeneratedsSuper._FixedOffsetTZ( + tzoff, results.group(0)) + input_data = input_data[:-6] + if len(input_data.split('.')) > 1: + dt = datetime_.datetime.strptime(input_data, '%H:%M:%S.%f') + else: + dt = datetime_.datetime.strptime(input_data, '%H:%M:%S') + dt = dt.replace(tzinfo=tz) + return dt.time() + def gds_str_lower(self, instring): + return instring.lower() + def get_path_(self, node): + path_list = [] + self.get_path_list_(node, path_list) + path_list.reverse() + path = '/'.join(path_list) + return path + Tag_strip_pattern_ = re_.compile(r'\{.*\}') + def get_path_list_(self, node, path_list): + if node is None: + return + tag = GeneratedsSuper.Tag_strip_pattern_.sub('', node.tag) + if tag: + path_list.append(tag) + self.get_path_list_(node.getparent(), path_list) + def get_class_obj_(self, node, default_class=None): + class_obj1 = default_class + if 'xsi' in node.nsmap: + classname = node.get('{%s}type' % node.nsmap['xsi']) + if classname is not None: + names = classname.split(':') + if len(names) == 2: + classname = names[1] + class_obj2 = globals().get(classname) + if class_obj2 is not None: + class_obj1 = class_obj2 + return class_obj1 + def gds_build_any(self, node, type_name=None): + return None + @classmethod + def gds_reverse_node_mapping(cls, mapping): + return dict(((v, k) for k, v in mapping.items())) + @staticmethod + def gds_encode(instring): + if sys.version_info.major == 2: + return instring.encode(ExternalEncoding) + else: + return instring + @staticmethod + def convert_unicode(instring): + if isinstance(instring, str): + result = quote_xml(instring) + elif sys.version_info.major == 2 and isinstance(instring, unicode): + result = quote_xml(instring).encode('utf8') + else: + result = GeneratedsSuper.gds_encode(str(instring)) + return result + def __eq__(self, other): + if type(self) != type(other): + return False + return self.__dict__ == other.__dict__ + def __ne__(self, other): + return not self.__eq__(other) + + def getSubclassFromModule_(module, class_): + '''Get the subclass of a class from a specific module.''' + name = class_.__name__ + 'Sub' + if hasattr(module, name): + return getattr(module, name) + else: + return None + + +# +# If you have installed IPython you can uncomment and use the following. +# IPython is available from http://ipython.scipy.org/. +# + +## from IPython.Shell import IPShellEmbed +## args = '' +## ipshell = IPShellEmbed(args, +## banner = 'Dropping into IPython', +## exit_msg = 'Leaving Interpreter, back to program.') + +# Then use the following line where and when you want to drop into the +# IPython shell: +# ipshell(' -- Entering ipshell.\nHit Ctrl-D to exit') + +# +# Globals +# + +ExternalEncoding = 'ascii' +Tag_pattern_ = re_.compile(r'({.*})?(.*)') +String_cleanup_pat_ = re_.compile(r"[\n\r\s]+") +Namespace_extract_pat_ = re_.compile(r'{(.*)}(.*)') +CDATA_pattern_ = re_.compile(r"", re_.DOTALL) + +# Change this to redirect the generated superclass module to use a +# specific subclass module. +CurrentSubclassModule_ = None + +# +# Support/utility functions. +# + + +def showIndent(outfile, level, pretty_print=True): + if pretty_print: + for idx in range(level): + outfile.write(' ') + + +def quote_xml(inStr): + "Escape markup chars, but do not modify CDATA sections." + if not inStr: + return '' + s1 = (isinstance(inStr, BaseStrType_) and inStr or '%s' % inStr) + s2 = '' + pos = 0 + matchobjects = CDATA_pattern_.finditer(s1) + for mo in matchobjects: + s3 = s1[pos:mo.start()] + s2 += quote_xml_aux(s3) + s2 += s1[mo.start():mo.end()] + pos = mo.end() + s3 = s1[pos:] + s2 += quote_xml_aux(s3) + return s2 + + +def quote_xml_aux(inStr): + s1 = inStr.replace('&', '&') + s1 = s1.replace('<', '<') + s1 = s1.replace('>', '>') + return s1 + + +def quote_attrib(inStr): + s1 = (isinstance(inStr, BaseStrType_) and inStr or '%s' % inStr) + s1 = s1.replace('&', '&') + s1 = s1.replace('<', '<') + s1 = s1.replace('>', '>') + if '"' in s1: + if "'" in s1: + s1 = '"%s"' % s1.replace('"', """) + else: + s1 = "'%s'" % s1 + else: + s1 = '"%s"' % s1 + return s1 + + +def quote_python(inStr): + s1 = inStr + if s1.find("'") == -1: + if s1.find('\n') == -1: + return "'%s'" % s1 + else: + return "'''%s'''" % s1 + else: + if s1.find('"') != -1: + s1 = s1.replace('"', '\\"') + if s1.find('\n') == -1: + return '"%s"' % s1 + else: + return '"""%s"""' % s1 + + +def get_all_text_(node): + if node.text is not None: + text = node.text + else: + text = '' + for child in node: + if child.tail is not None: + text += child.tail + return text + + +def find_attr_value_(attr_name, node): + attrs = node.attrib + attr_parts = attr_name.split(':') + value = None + if len(attr_parts) == 1: + value = attrs.get(attr_name) + elif len(attr_parts) == 2: + prefix, name = attr_parts + namespace = node.nsmap.get(prefix) + if namespace is not None: + value = attrs.get('{%s}%s' % (namespace, name, )) + return value + + +class GDSParseError(Exception): + pass + + +def raise_parse_error(node, msg): + msg = '%s (element %s/line %d)' % (msg, node.tag, node.sourceline, ) + raise GDSParseError(msg) + + +class MixedContainer: + # Constants for category: + CategoryNone = 0 + CategoryText = 1 + CategorySimple = 2 + CategoryComplex = 3 + # Constants for content_type: + TypeNone = 0 + TypeText = 1 + TypeString = 2 + TypeInteger = 3 + TypeFloat = 4 + TypeDecimal = 5 + TypeDouble = 6 + TypeBoolean = 7 + TypeBase64 = 8 + def __init__(self, category, content_type, name, value): + self.category = category + self.content_type = content_type + self.name = name + self.value = value + def getCategory(self): + return self.category + def getContenttype(self, content_type): + return self.content_type + def getValue(self): + return self.value + def getName(self): + return self.name + def export(self, outfile, level, name, namespace, + pretty_print=True): + if self.category == MixedContainer.CategoryText: + # Prevent exporting empty content as empty lines. + if self.value.strip(): + outfile.write(self.value) + elif self.category == MixedContainer.CategorySimple: + self.exportSimple(outfile, level, name) + else: # category == MixedContainer.CategoryComplex + self.value.export( + outfile, level, namespace, name, + pretty_print=pretty_print) + def exportSimple(self, outfile, level, name): + if self.content_type == MixedContainer.TypeString: + outfile.write('<%s>%s' % ( + self.name, self.value, self.name)) + elif self.content_type == MixedContainer.TypeInteger or \ + self.content_type == MixedContainer.TypeBoolean: + outfile.write('<%s>%d' % ( + self.name, self.value, self.name)) + elif self.content_type == MixedContainer.TypeFloat or \ + self.content_type == MixedContainer.TypeDecimal: + outfile.write('<%s>%f' % ( + self.name, self.value, self.name)) + elif self.content_type == MixedContainer.TypeDouble: + outfile.write('<%s>%g' % ( + self.name, self.value, self.name)) + elif self.content_type == MixedContainer.TypeBase64: + outfile.write('<%s>%s' % ( + self.name, + base64.b64encode(self.value), + self.name)) + def to_etree(self, element): + if self.category == MixedContainer.CategoryText: + # Prevent exporting empty content as empty lines. + if self.value.strip(): + if len(element) > 0: + if element[-1].tail is None: + element[-1].tail = self.value + else: + element[-1].tail += self.value + else: + if element.text is None: + element.text = self.value + else: + element.text += self.value + elif self.category == MixedContainer.CategorySimple: + subelement = etree_.SubElement( + element, '%s' % self.name) + subelement.text = self.to_etree_simple() + else: # category == MixedContainer.CategoryComplex + self.value.to_etree(element) + def to_etree_simple(self): + if self.content_type == MixedContainer.TypeString: + text = self.value + elif (self.content_type == MixedContainer.TypeInteger or + self.content_type == MixedContainer.TypeBoolean): + text = '%d' % self.value + elif (self.content_type == MixedContainer.TypeFloat or + self.content_type == MixedContainer.TypeDecimal): + text = '%f' % self.value + elif self.content_type == MixedContainer.TypeDouble: + text = '%g' % self.value + elif self.content_type == MixedContainer.TypeBase64: + text = '%s' % base64.b64encode(self.value) + return text + def exportLiteral(self, outfile, level, name): + if self.category == MixedContainer.CategoryText: + showIndent(outfile, level) + outfile.write( + 'model_.MixedContainer(%d, %d, "%s", "%s"),\n' % ( + self.category, self.content_type, + self.name, self.value)) + elif self.category == MixedContainer.CategorySimple: + showIndent(outfile, level) + outfile.write( + 'model_.MixedContainer(%d, %d, "%s", "%s"),\n' % ( + self.category, self.content_type, + self.name, self.value)) + else: # category == MixedContainer.CategoryComplex + showIndent(outfile, level) + outfile.write( + 'model_.MixedContainer(%d, %d, "%s",\n' % ( + self.category, self.content_type, self.name,)) + self.value.exportLiteral(outfile, level + 1) + showIndent(outfile, level) + outfile.write(')\n') + + +class MemberSpec_(object): + def __init__(self, name='', data_type='', container=0, + optional=0, child_attrs=None, choice=None): + self.name = name + self.data_type = data_type + self.container = container + self.child_attrs = child_attrs + self.choice = choice + self.optional = optional + def set_name(self, name): self.name = name + def get_name(self): return self.name + def set_data_type(self, data_type): self.data_type = data_type + def get_data_type_chain(self): return self.data_type + def get_data_type(self): + if isinstance(self.data_type, list): + if len(self.data_type) > 0: + return self.data_type[-1] + else: + return 'xs:string' + else: + return self.data_type + def set_container(self, container): self.container = container + def get_container(self): return self.container + def set_child_attrs(self, child_attrs): self.child_attrs = child_attrs + def get_child_attrs(self): return self.child_attrs + def set_choice(self, choice): self.choice = choice + def get_choice(self): return self.choice + def set_optional(self, optional): self.optional = optional + def get_optional(self): return self.optional + + +def _cast(typ, value): + if typ is None or value is None: + return value + return typ(value) + +# +# Data representation classes. +# + + +class oadrPayload(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Signature=None, oadrSignedObject=None): + self.original_tagname_ = None + self.Signature = Signature + self.oadrSignedObject = oadrSignedObject + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrPayload) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrPayload.subclass: + return oadrPayload.subclass(*args_, **kwargs_) + else: + return oadrPayload(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Signature(self): return self.Signature + def set_Signature(self, Signature): self.Signature = Signature + def get_oadrSignedObject(self): return self.oadrSignedObject + def set_oadrSignedObject(self, oadrSignedObject): self.oadrSignedObject = oadrSignedObject + def hasContent_(self): + if ( + self.Signature is not None or + self.oadrSignedObject is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrPayload', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrPayload') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrPayload') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrPayload', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrPayload'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrPayload', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Signature is not None: + self.Signature.export(outfile, level, namespace_='ds:', name_='Signature', pretty_print=pretty_print) + if self.oadrSignedObject is not None: + self.oadrSignedObject.export(outfile, level, namespace_='oadr:', name_='oadrSignedObject', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Signature': + obj_ = SignatureType.factory() + obj_.build(child_) + self.Signature = obj_ + obj_.original_tagname_ = 'Signature' + elif nodeName_ == 'oadrSignedObject': + obj_ = oadrSignedObject.factory() + obj_.build(child_) + self.oadrSignedObject = obj_ + obj_.original_tagname_ = 'oadrSignedObject' +# end class oadrPayload + + +class oadrSignedObject(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, oadrDistributeEvent=None, oadrCreatedEvent=None, oadrRequestEvent=None, oadrResponse=None, oadrCancelOpt=None, oadrCanceledOpt=None, oadrCreateOpt=None, oadrCreatedOpt=None, oadrCancelReport=None, oadrCanceledReport=None, oadrCreateReport=None, oadrCreatedReport=None, oadrRegisterReport=None, oadrRegisteredReport=None, oadrUpdateReport=None, oadrUpdatedReport=None, oadrCancelPartyRegistration=None, oadrCanceledPartyRegistration=None, oadrCreatePartyRegistration=None, oadrCreatedPartyRegistration=None, oadrRequestReregistration=None, oadrQueryRegistration=None, oadrPoll=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.oadrDistributeEvent = oadrDistributeEvent + self.oadrCreatedEvent = oadrCreatedEvent + self.oadrRequestEvent = oadrRequestEvent + self.oadrResponse = oadrResponse + self.oadrCancelOpt = oadrCancelOpt + self.oadrCanceledOpt = oadrCanceledOpt + self.oadrCreateOpt = oadrCreateOpt + self.oadrCreatedOpt = oadrCreatedOpt + self.oadrCancelReport = oadrCancelReport + self.oadrCanceledReport = oadrCanceledReport + self.oadrCreateReport = oadrCreateReport + self.oadrCreatedReport = oadrCreatedReport + self.oadrRegisterReport = oadrRegisterReport + self.oadrRegisteredReport = oadrRegisteredReport + self.oadrUpdateReport = oadrUpdateReport + self.oadrUpdatedReport = oadrUpdatedReport + self.oadrCancelPartyRegistration = oadrCancelPartyRegistration + self.oadrCanceledPartyRegistration = oadrCanceledPartyRegistration + self.oadrCreatePartyRegistration = oadrCreatePartyRegistration + self.oadrCreatedPartyRegistration = oadrCreatedPartyRegistration + self.oadrRequestReregistration = oadrRequestReregistration + self.oadrQueryRegistration = oadrQueryRegistration + self.oadrPoll = oadrPoll + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrSignedObject) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrSignedObject.subclass: + return oadrSignedObject.subclass(*args_, **kwargs_) + else: + return oadrSignedObject(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrDistributeEvent(self): return self.oadrDistributeEvent + def set_oadrDistributeEvent(self, oadrDistributeEvent): self.oadrDistributeEvent = oadrDistributeEvent + def get_oadrCreatedEvent(self): return self.oadrCreatedEvent + def set_oadrCreatedEvent(self, oadrCreatedEvent): self.oadrCreatedEvent = oadrCreatedEvent + def get_oadrRequestEvent(self): return self.oadrRequestEvent + def set_oadrRequestEvent(self, oadrRequestEvent): self.oadrRequestEvent = oadrRequestEvent + def get_oadrResponse(self): return self.oadrResponse + def set_oadrResponse(self, oadrResponse): self.oadrResponse = oadrResponse + def get_oadrCancelOpt(self): return self.oadrCancelOpt + def set_oadrCancelOpt(self, oadrCancelOpt): self.oadrCancelOpt = oadrCancelOpt + def get_oadrCanceledOpt(self): return self.oadrCanceledOpt + def set_oadrCanceledOpt(self, oadrCanceledOpt): self.oadrCanceledOpt = oadrCanceledOpt + def get_oadrCreateOpt(self): return self.oadrCreateOpt + def set_oadrCreateOpt(self, oadrCreateOpt): self.oadrCreateOpt = oadrCreateOpt + def get_oadrCreatedOpt(self): return self.oadrCreatedOpt + def set_oadrCreatedOpt(self, oadrCreatedOpt): self.oadrCreatedOpt = oadrCreatedOpt + def get_oadrCancelReport(self): return self.oadrCancelReport + def set_oadrCancelReport(self, oadrCancelReport): self.oadrCancelReport = oadrCancelReport + def get_oadrCanceledReport(self): return self.oadrCanceledReport + def set_oadrCanceledReport(self, oadrCanceledReport): self.oadrCanceledReport = oadrCanceledReport + def get_oadrCreateReport(self): return self.oadrCreateReport + def set_oadrCreateReport(self, oadrCreateReport): self.oadrCreateReport = oadrCreateReport + def get_oadrCreatedReport(self): return self.oadrCreatedReport + def set_oadrCreatedReport(self, oadrCreatedReport): self.oadrCreatedReport = oadrCreatedReport + def get_oadrRegisterReport(self): return self.oadrRegisterReport + def set_oadrRegisterReport(self, oadrRegisterReport): self.oadrRegisterReport = oadrRegisterReport + def get_oadrRegisteredReport(self): return self.oadrRegisteredReport + def set_oadrRegisteredReport(self, oadrRegisteredReport): self.oadrRegisteredReport = oadrRegisteredReport + def get_oadrUpdateReport(self): return self.oadrUpdateReport + def set_oadrUpdateReport(self, oadrUpdateReport): self.oadrUpdateReport = oadrUpdateReport + def get_oadrUpdatedReport(self): return self.oadrUpdatedReport + def set_oadrUpdatedReport(self, oadrUpdatedReport): self.oadrUpdatedReport = oadrUpdatedReport + def get_oadrCancelPartyRegistration(self): return self.oadrCancelPartyRegistration + def set_oadrCancelPartyRegistration(self, oadrCancelPartyRegistration): self.oadrCancelPartyRegistration = oadrCancelPartyRegistration + def get_oadrCanceledPartyRegistration(self): return self.oadrCanceledPartyRegistration + def set_oadrCanceledPartyRegistration(self, oadrCanceledPartyRegistration): self.oadrCanceledPartyRegistration = oadrCanceledPartyRegistration + def get_oadrCreatePartyRegistration(self): return self.oadrCreatePartyRegistration + def set_oadrCreatePartyRegistration(self, oadrCreatePartyRegistration): self.oadrCreatePartyRegistration = oadrCreatePartyRegistration + def get_oadrCreatedPartyRegistration(self): return self.oadrCreatedPartyRegistration + def set_oadrCreatedPartyRegistration(self, oadrCreatedPartyRegistration): self.oadrCreatedPartyRegistration = oadrCreatedPartyRegistration + def get_oadrRequestReregistration(self): return self.oadrRequestReregistration + def set_oadrRequestReregistration(self, oadrRequestReregistration): self.oadrRequestReregistration = oadrRequestReregistration + def get_oadrQueryRegistration(self): return self.oadrQueryRegistration + def set_oadrQueryRegistration(self, oadrQueryRegistration): self.oadrQueryRegistration = oadrQueryRegistration + def get_oadrPoll(self): return self.oadrPoll + def set_oadrPoll(self, oadrPoll): self.oadrPoll = oadrPoll + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + self.oadrDistributeEvent is not None or + self.oadrCreatedEvent is not None or + self.oadrRequestEvent is not None or + self.oadrResponse is not None or + self.oadrCancelOpt is not None or + self.oadrCanceledOpt is not None or + self.oadrCreateOpt is not None or + self.oadrCreatedOpt is not None or + self.oadrCancelReport is not None or + self.oadrCanceledReport is not None or + self.oadrCreateReport is not None or + self.oadrCreatedReport is not None or + self.oadrRegisterReport is not None or + self.oadrRegisteredReport is not None or + self.oadrUpdateReport is not None or + self.oadrUpdatedReport is not None or + self.oadrCancelPartyRegistration is not None or + self.oadrCanceledPartyRegistration is not None or + self.oadrCreatePartyRegistration is not None or + self.oadrCreatedPartyRegistration is not None or + self.oadrRequestReregistration is not None or + self.oadrQueryRegistration is not None or + self.oadrPoll is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrSignedObject', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrSignedObject') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrSignedObject') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrSignedObject', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrSignedObject'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.Id), input_name='Id')), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrSignedObject', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrDistributeEvent is not None: + self.oadrDistributeEvent.export(outfile, level, namespace_='oadr:', name_='oadrDistributeEvent', pretty_print=pretty_print) + if self.oadrCreatedEvent is not None: + self.oadrCreatedEvent.export(outfile, level, namespace_='oadr:', name_='oadrCreatedEvent', pretty_print=pretty_print) + if self.oadrRequestEvent is not None: + self.oadrRequestEvent.export(outfile, level, namespace_='oadr:', name_='oadrRequestEvent', pretty_print=pretty_print) + if self.oadrResponse is not None: + self.oadrResponse.export(outfile, level, namespace_='oadr:', name_='oadrResponse', pretty_print=pretty_print) + if self.oadrCancelOpt is not None: + self.oadrCancelOpt.export(outfile, level, namespace_='oadr:', name_='oadrCancelOpt', pretty_print=pretty_print) + if self.oadrCanceledOpt is not None: + self.oadrCanceledOpt.export(outfile, level, namespace_='oadr:', name_='oadrCanceledOpt', pretty_print=pretty_print) + if self.oadrCreateOpt is not None: + self.oadrCreateOpt.export(outfile, level, namespace_='oadr:', name_='oadrCreateOpt', pretty_print=pretty_print) + if self.oadrCreatedOpt is not None: + self.oadrCreatedOpt.export(outfile, level, namespace_='oadr:', name_='oadrCreatedOpt', pretty_print=pretty_print) + if self.oadrCancelReport is not None: + self.oadrCancelReport.export(outfile, level, namespace_='oadr:', name_='oadrCancelReport', pretty_print=pretty_print) + if self.oadrCanceledReport is not None: + self.oadrCanceledReport.export(outfile, level, namespace_='oadr:', name_='oadrCanceledReport', pretty_print=pretty_print) + if self.oadrCreateReport is not None: + self.oadrCreateReport.export(outfile, level, namespace_='oadr:', name_='oadrCreateReport', pretty_print=pretty_print) + if self.oadrCreatedReport is not None: + self.oadrCreatedReport.export(outfile, level, namespace_='oadr:', name_='oadrCreatedReport', pretty_print=pretty_print) + if self.oadrRegisterReport is not None: + self.oadrRegisterReport.export(outfile, level, namespace_='oadr:', name_='oadrRegisterReport', pretty_print=pretty_print) + if self.oadrRegisteredReport is not None: + self.oadrRegisteredReport.export(outfile, level, namespace_='oadr:', name_='oadrRegisteredReport', pretty_print=pretty_print) + if self.oadrUpdateReport is not None: + self.oadrUpdateReport.export(outfile, level, namespace_='oadr:', name_='oadrUpdateReport', pretty_print=pretty_print) + if self.oadrUpdatedReport is not None: + self.oadrUpdatedReport.export(outfile, level, namespace_='oadr:', name_='oadrUpdatedReport', pretty_print=pretty_print) + if self.oadrCancelPartyRegistration is not None: + self.oadrCancelPartyRegistration.export(outfile, level, namespace_='oadr:', name_='oadrCancelPartyRegistration', pretty_print=pretty_print) + if self.oadrCanceledPartyRegistration is not None: + self.oadrCanceledPartyRegistration.export(outfile, level, namespace_='oadr:', name_='oadrCanceledPartyRegistration', pretty_print=pretty_print) + if self.oadrCreatePartyRegistration is not None: + self.oadrCreatePartyRegistration.export(outfile, level, namespace_='oadr:', name_='oadrCreatePartyRegistration', pretty_print=pretty_print) + if self.oadrCreatedPartyRegistration is not None: + self.oadrCreatedPartyRegistration.export(outfile, level, namespace_='oadr:', name_='oadrCreatedPartyRegistration', pretty_print=pretty_print) + if self.oadrRequestReregistration is not None: + self.oadrRequestReregistration.export(outfile, level, namespace_='oadr:', name_='oadrRequestReregistration', pretty_print=pretty_print) + if self.oadrQueryRegistration is not None: + self.oadrQueryRegistration.export(outfile, level, namespace_='oadr:', name_='oadrQueryRegistration', pretty_print=pretty_print) + if self.oadrPoll is not None: + self.oadrPoll.export(outfile, level, namespace_='oadr:', name_='oadrPoll', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrDistributeEvent': + obj_ = oadrDistributeEventType.factory() + obj_.build(child_) + self.oadrDistributeEvent = obj_ + obj_.original_tagname_ = 'oadrDistributeEvent' + elif nodeName_ == 'oadrCreatedEvent': + obj_ = oadrCreatedEventType.factory() + obj_.build(child_) + self.oadrCreatedEvent = obj_ + obj_.original_tagname_ = 'oadrCreatedEvent' + elif nodeName_ == 'oadrRequestEvent': + obj_ = oadrRequestEventType.factory() + obj_.build(child_) + self.oadrRequestEvent = obj_ + obj_.original_tagname_ = 'oadrRequestEvent' + elif nodeName_ == 'oadrResponse': + obj_ = oadrResponseType.factory() + obj_.build(child_) + self.oadrResponse = obj_ + obj_.original_tagname_ = 'oadrResponse' + elif nodeName_ == 'oadrCancelOpt': + obj_ = oadrCancelOptType.factory() + obj_.build(child_) + self.oadrCancelOpt = obj_ + obj_.original_tagname_ = 'oadrCancelOpt' + elif nodeName_ == 'oadrCanceledOpt': + obj_ = oadrCanceledOptType.factory() + obj_.build(child_) + self.oadrCanceledOpt = obj_ + obj_.original_tagname_ = 'oadrCanceledOpt' + elif nodeName_ == 'oadrCreateOpt': + obj_ = oadrCreateOptType.factory() + obj_.build(child_) + self.oadrCreateOpt = obj_ + obj_.original_tagname_ = 'oadrCreateOpt' + elif nodeName_ == 'oadrCreatedOpt': + obj_ = oadrCreatedOptType.factory() + obj_.build(child_) + self.oadrCreatedOpt = obj_ + obj_.original_tagname_ = 'oadrCreatedOpt' + elif nodeName_ == 'oadrCancelReport': + obj_ = oadrCancelReportType.factory() + obj_.build(child_) + self.oadrCancelReport = obj_ + obj_.original_tagname_ = 'oadrCancelReport' + elif nodeName_ == 'oadrCanceledReport': + obj_ = oadrCanceledReportType.factory() + obj_.build(child_) + self.oadrCanceledReport = obj_ + obj_.original_tagname_ = 'oadrCanceledReport' + elif nodeName_ == 'oadrCreateReport': + obj_ = oadrCreateReportType.factory() + obj_.build(child_) + self.oadrCreateReport = obj_ + obj_.original_tagname_ = 'oadrCreateReport' + elif nodeName_ == 'oadrCreatedReport': + obj_ = oadrCreatedReportType.factory() + obj_.build(child_) + self.oadrCreatedReport = obj_ + obj_.original_tagname_ = 'oadrCreatedReport' + elif nodeName_ == 'oadrRegisterReport': + obj_ = oadrRegisterReportType.factory() + obj_.build(child_) + self.oadrRegisterReport = obj_ + obj_.original_tagname_ = 'oadrRegisterReport' + elif nodeName_ == 'oadrRegisteredReport': + obj_ = oadrRegisteredReportType.factory() + obj_.build(child_) + self.oadrRegisteredReport = obj_ + obj_.original_tagname_ = 'oadrRegisteredReport' + elif nodeName_ == 'oadrUpdateReport': + obj_ = oadrUpdateReportType.factory() + obj_.build(child_) + self.oadrUpdateReport = obj_ + obj_.original_tagname_ = 'oadrUpdateReport' + elif nodeName_ == 'oadrUpdatedReport': + obj_ = oadrUpdatedReportType.factory() + obj_.build(child_) + self.oadrUpdatedReport = obj_ + obj_.original_tagname_ = 'oadrUpdatedReport' + elif nodeName_ == 'oadrCancelPartyRegistration': + obj_ = oadrCancelPartyRegistrationType.factory() + obj_.build(child_) + self.oadrCancelPartyRegistration = obj_ + obj_.original_tagname_ = 'oadrCancelPartyRegistration' + elif nodeName_ == 'oadrCanceledPartyRegistration': + obj_ = oadrCanceledPartyRegistrationType.factory() + obj_.build(child_) + self.oadrCanceledPartyRegistration = obj_ + obj_.original_tagname_ = 'oadrCanceledPartyRegistration' + elif nodeName_ == 'oadrCreatePartyRegistration': + obj_ = oadrCreatePartyRegistrationType.factory() + obj_.build(child_) + self.oadrCreatePartyRegistration = obj_ + obj_.original_tagname_ = 'oadrCreatePartyRegistration' + elif nodeName_ == 'oadrCreatedPartyRegistration': + obj_ = oadrCreatedPartyRegistrationType.factory() + obj_.build(child_) + self.oadrCreatedPartyRegistration = obj_ + obj_.original_tagname_ = 'oadrCreatedPartyRegistration' + elif nodeName_ == 'oadrRequestReregistration': + obj_ = oadrRequestReregistrationType.factory() + obj_.build(child_) + self.oadrRequestReregistration = obj_ + obj_.original_tagname_ = 'oadrRequestReregistration' + elif nodeName_ == 'oadrQueryRegistration': + obj_ = oadrQueryRegistrationType.factory() + obj_.build(child_) + self.oadrQueryRegistration = obj_ + obj_.original_tagname_ = 'oadrQueryRegistration' + elif nodeName_ == 'oadrPoll': + obj_ = oadrPollType.factory() + obj_.build(child_) + self.oadrPoll = obj_ + obj_.original_tagname_ = 'oadrPoll' +# end class oadrSignedObject + + +class oadrDistributeEventType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, requestID=None, vtnID=None, oadrEvent=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.requestID = requestID + self.vtnID = vtnID + if oadrEvent is None: + self.oadrEvent = [] + else: + self.oadrEvent = oadrEvent + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrDistributeEventType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrDistributeEventType.subclass: + return oadrDistributeEventType.subclass(*args_, **kwargs_) + else: + return oadrDistributeEventType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_vtnID(self): return self.vtnID + def set_vtnID(self, vtnID): self.vtnID = vtnID + def get_oadrEvent(self): return self.oadrEvent + def set_oadrEvent(self, oadrEvent): self.oadrEvent = oadrEvent + def add_oadrEvent(self, value): self.oadrEvent.append(value) + def insert_oadrEvent_at(self, index, value): self.oadrEvent.insert(index, value) + def replace_oadrEvent_at(self, index, value): self.oadrEvent[index] = value + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.requestID is not None or + self.vtnID is not None or + self.oadrEvent + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrDistributeEventType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrDistributeEventType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrDistributeEventType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrDistributeEventType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrDistributeEventType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrDistributeEventType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.vtnID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.vtnID), input_name='vtnID')), eol_)) + for oadrEvent_ in self.oadrEvent: + oadrEvent_.export(outfile, level, namespace_, name_='oadrEvent', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'vtnID': + vtnID_ = child_.text + vtnID_ = self.gds_validate_string(vtnID_, node, 'vtnID') + self.vtnID = vtnID_ + elif nodeName_ == 'oadrEvent': + obj_ = oadrEventType.factory() + obj_.build(child_) + self.oadrEvent.append(obj_) + obj_.original_tagname_ = 'oadrEvent' +# end class oadrDistributeEventType + + +class oadrCreatedEventType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiCreatedEvent=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiCreatedEvent = eiCreatedEvent + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreatedEventType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreatedEventType.subclass: + return oadrCreatedEventType.subclass(*args_, **kwargs_) + else: + return oadrCreatedEventType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiCreatedEvent(self): return self.eiCreatedEvent + def set_eiCreatedEvent(self, eiCreatedEvent): self.eiCreatedEvent = eiCreatedEvent + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiCreatedEvent is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreatedEventType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreatedEventType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreatedEventType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreatedEventType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreatedEventType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreatedEventType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiCreatedEvent is not None: + self.eiCreatedEvent.export(outfile, level, namespace_='pyld:', name_='eiCreatedEvent', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiCreatedEvent': + obj_ = eiCreatedEvent.factory() + obj_.build(child_) + self.eiCreatedEvent = obj_ + obj_.original_tagname_ = 'eiCreatedEvent' +# end class oadrCreatedEventType + + +class oadrRequestEventType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiRequestEvent=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiRequestEvent = eiRequestEvent + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrRequestEventType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrRequestEventType.subclass: + return oadrRequestEventType.subclass(*args_, **kwargs_) + else: + return oadrRequestEventType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiRequestEvent(self): return self.eiRequestEvent + def set_eiRequestEvent(self, eiRequestEvent): self.eiRequestEvent = eiRequestEvent + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiRequestEvent is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrRequestEventType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrRequestEventType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrRequestEventType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrRequestEventType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrRequestEventType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrRequestEventType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiRequestEvent is not None: + self.eiRequestEvent.export(outfile, level, namespace_='pyld:', name_='eiRequestEvent', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiRequestEvent': + obj_ = eiRequestEvent.factory() + obj_.build(child_) + self.eiRequestEvent = obj_ + obj_.original_tagname_ = 'eiRequestEvent' +# end class oadrRequestEventType + + +class oadrResponseType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrResponseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrResponseType.subclass: + return oadrResponseType.subclass(*args_, **kwargs_) + else: + return oadrResponseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrResponseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrResponseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrResponseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrResponseType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrResponseType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrResponseType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrResponseType + + +class oadrCancelOptType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, optID=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + self.optID = optID + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCancelOptType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCancelOptType.subclass: + return oadrCancelOptType.subclass(*args_, **kwargs_) + else: + return oadrCancelOptType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_optID(self): return self.optID + def set_optID(self, optID): self.optID = optID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.optID is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCancelOptType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCancelOptType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCancelOptType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCancelOptType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCancelOptType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCancelOptType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.optID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optID), input_name='optID')), eol_)) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'optID': + optID_ = child_.text + optID_ = self.gds_validate_string(optID_, node, 'optID') + self.optID = optID_ + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCancelOptType + + +class oadrCanceledOptType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, optID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.optID = optID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCanceledOptType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCanceledOptType.subclass: + return oadrCanceledOptType.subclass(*args_, **kwargs_) + else: + return oadrCanceledOptType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_optID(self): return self.optID + def set_optID(self, optID): self.optID = optID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.optID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCanceledOptType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCanceledOptType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCanceledOptType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCanceledOptType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCanceledOptType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCanceledOptType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.optID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optID), input_name='optID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'optID': + optID_ = child_.text + optID_ = self.gds_validate_string(optID_, node, 'optID') + self.optID = optID_ +# end class oadrCanceledOptType + + +class oadrCreatedOptType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, optID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.optID = optID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreatedOptType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreatedOptType.subclass: + return oadrCreatedOptType.subclass(*args_, **kwargs_) + else: + return oadrCreatedOptType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_optID(self): return self.optID + def set_optID(self, optID): self.optID = optID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.optID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreatedOptType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreatedOptType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreatedOptType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreatedOptType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreatedOptType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreatedOptType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.optID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optID), input_name='optID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'optID': + optID_ = child_.text + optID_ = self.gds_validate_string(optID_, node, 'optID') + self.optID = optID_ +# end class oadrCreatedOptType + + +class oadrCancelReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, reportRequestID=None, reportToFollow=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + if reportRequestID is None: + self.reportRequestID = [] + else: + self.reportRequestID = reportRequestID + self.reportToFollow = reportToFollow + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCancelReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCancelReportType.subclass: + return oadrCancelReportType.subclass(*args_, **kwargs_) + else: + return oadrCancelReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_reportRequestID(self): return self.reportRequestID + def set_reportRequestID(self, reportRequestID): self.reportRequestID = reportRequestID + def add_reportRequestID(self, value): self.reportRequestID.append(value) + def insert_reportRequestID_at(self, index, value): self.reportRequestID.insert(index, value) + def replace_reportRequestID_at(self, index, value): self.reportRequestID[index] = value + def get_reportToFollow(self): return self.reportToFollow + def set_reportToFollow(self, reportToFollow): self.reportToFollow = reportToFollow + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.reportRequestID or + self.reportToFollow is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCancelReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCancelReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCancelReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCancelReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCancelReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCancelReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + for reportRequestID_ in self.reportRequestID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(reportRequestID_), input_name='reportRequestID')), eol_)) + if self.reportToFollow is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.reportToFollow, input_name='reportToFollow'), eol_)) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'reportRequestID': + reportRequestID_ = child_.text + reportRequestID_ = self.gds_validate_string(reportRequestID_, node, 'reportRequestID') + self.reportRequestID.append(reportRequestID_) + elif nodeName_ == 'reportToFollow': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'reportToFollow') + self.reportToFollow = ival_ + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCancelReportType + + +class oadrCanceledReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, oadrPendingReports=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.oadrPendingReports = oadrPendingReports + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCanceledReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCanceledReportType.subclass: + return oadrCanceledReportType.subclass(*args_, **kwargs_) + else: + return oadrCanceledReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_oadrPendingReports(self): return self.oadrPendingReports + def set_oadrPendingReports(self, oadrPendingReports): self.oadrPendingReports = oadrPendingReports + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.oadrPendingReports is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCanceledReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCanceledReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCanceledReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCanceledReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCanceledReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCanceledReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.oadrPendingReports is not None: + self.oadrPendingReports.export(outfile, level, namespace_='oadr:', name_='oadrPendingReports', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'oadrPendingReports': + obj_ = oadrPendingReportsType.factory() + obj_.build(child_) + self.oadrPendingReports = obj_ + obj_.original_tagname_ = 'oadrPendingReports' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCanceledReportType + + +class oadrCreateReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, oadrReportRequest=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + if oadrReportRequest is None: + self.oadrReportRequest = [] + else: + self.oadrReportRequest = oadrReportRequest + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreateReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreateReportType.subclass: + return oadrCreateReportType.subclass(*args_, **kwargs_) + else: + return oadrCreateReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_oadrReportRequest(self): return self.oadrReportRequest + def set_oadrReportRequest(self, oadrReportRequest): self.oadrReportRequest = oadrReportRequest + def add_oadrReportRequest(self, value): self.oadrReportRequest.append(value) + def insert_oadrReportRequest_at(self, index, value): self.oadrReportRequest.insert(index, value) + def replace_oadrReportRequest_at(self, index, value): self.oadrReportRequest[index] = value + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.oadrReportRequest or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreateReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreateReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreateReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreateReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreateReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreateReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + for oadrReportRequest_ in self.oadrReportRequest: + oadrReportRequest_.export(outfile, level, namespace_='oadr:', name_='oadrReportRequest', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'oadrReportRequest': + obj_ = oadrReportRequestType.factory() + obj_.build(child_) + self.oadrReportRequest.append(obj_) + obj_.original_tagname_ = 'oadrReportRequest' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCreateReportType + + +class oadrCreatedReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, oadrPendingReports=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.oadrPendingReports = oadrPendingReports + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreatedReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreatedReportType.subclass: + return oadrCreatedReportType.subclass(*args_, **kwargs_) + else: + return oadrCreatedReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_oadrPendingReports(self): return self.oadrPendingReports + def set_oadrPendingReports(self, oadrPendingReports): self.oadrPendingReports = oadrPendingReports + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.oadrPendingReports is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreatedReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreatedReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreatedReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreatedReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreatedReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreatedReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.oadrPendingReports is not None: + self.oadrPendingReports.export(outfile, level, namespace_='oadr:', name_='oadrPendingReports', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'oadrPendingReports': + obj_ = oadrPendingReportsType.factory() + obj_.build(child_) + self.oadrPendingReports = obj_ + obj_.original_tagname_ = 'oadrPendingReports' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCreatedReportType + + +class oadrRegisterReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, oadrReport=None, venID=None, reportRequestID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + if oadrReport is None: + self.oadrReport = [] + else: + self.oadrReport = oadrReport + self.venID = venID + self.reportRequestID = reportRequestID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrRegisterReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrRegisterReportType.subclass: + return oadrRegisterReportType.subclass(*args_, **kwargs_) + else: + return oadrRegisterReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_oadrReport(self): return self.oadrReport + def set_oadrReport(self, oadrReport): self.oadrReport = oadrReport + def add_oadrReport(self, value): self.oadrReport.append(value) + def insert_oadrReport_at(self, index, value): self.oadrReport.insert(index, value) + def replace_oadrReport_at(self, index, value): self.oadrReport[index] = value + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_reportRequestID(self): return self.reportRequestID + def set_reportRequestID(self, reportRequestID): self.reportRequestID = reportRequestID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.oadrReport or + self.venID is not None or + self.reportRequestID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrRegisterReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrRegisterReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrRegisterReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrRegisterReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrRegisterReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrRegisterReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + for oadrReport_ in self.oadrReport: + oadrReport_.export(outfile, level, namespace_='oadr:', name_='oadrReport', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + if self.reportRequestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportRequestID), input_name='reportRequestID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'oadrReport': + obj_ = oadrReportType.factory() + obj_.build(child_) + self.oadrReport.append(obj_) + obj_.original_tagname_ = 'oadrReport' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ + elif nodeName_ == 'reportRequestID': + reportRequestID_ = child_.text + reportRequestID_ = self.gds_validate_string(reportRequestID_, node, 'reportRequestID') + self.reportRequestID = reportRequestID_ +# end class oadrRegisterReportType + + +class oadrRegisteredReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, oadrReportRequest=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + if oadrReportRequest is None: + self.oadrReportRequest = [] + else: + self.oadrReportRequest = oadrReportRequest + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrRegisteredReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrRegisteredReportType.subclass: + return oadrRegisteredReportType.subclass(*args_, **kwargs_) + else: + return oadrRegisteredReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_oadrReportRequest(self): return self.oadrReportRequest + def set_oadrReportRequest(self, oadrReportRequest): self.oadrReportRequest = oadrReportRequest + def add_oadrReportRequest(self, value): self.oadrReportRequest.append(value) + def insert_oadrReportRequest_at(self, index, value): self.oadrReportRequest.insert(index, value) + def replace_oadrReportRequest_at(self, index, value): self.oadrReportRequest[index] = value + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.oadrReportRequest or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrRegisteredReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrRegisteredReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrRegisteredReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrRegisteredReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrRegisteredReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrRegisteredReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + for oadrReportRequest_ in self.oadrReportRequest: + oadrReportRequest_.export(outfile, level, namespace_='oadr:', name_='oadrReportRequest', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'oadrReportRequest': + obj_ = oadrReportRequestType.factory() + obj_.build(child_) + self.oadrReportRequest.append(obj_) + obj_.original_tagname_ = 'oadrReportRequest' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrRegisteredReportType + + +class oadrUpdateReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, oadrReport=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + if oadrReport is None: + self.oadrReport = [] + else: + self.oadrReport = oadrReport + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrUpdateReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrUpdateReportType.subclass: + return oadrUpdateReportType.subclass(*args_, **kwargs_) + else: + return oadrUpdateReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_oadrReport(self): return self.oadrReport + def set_oadrReport(self, oadrReport): self.oadrReport = oadrReport + def add_oadrReport(self, value): self.oadrReport.append(value) + def insert_oadrReport_at(self, index, value): self.oadrReport.insert(index, value) + def replace_oadrReport_at(self, index, value): self.oadrReport[index] = value + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.oadrReport or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrUpdateReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrUpdateReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrUpdateReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrUpdateReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrUpdateReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrUpdateReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + for oadrReport_ in self.oadrReport: + oadrReport_.export(outfile, level, namespace_='oadr:', name_='oadrReport', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'oadrReport': + obj_ = oadrReportType.factory() + obj_.build(child_) + self.oadrReport.append(obj_) + obj_.original_tagname_ = 'oadrReport' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrUpdateReportType + + +class oadrUpdatedReportType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, oadrCancelReport=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.oadrCancelReport = oadrCancelReport + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrUpdatedReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrUpdatedReportType.subclass: + return oadrUpdatedReportType.subclass(*args_, **kwargs_) + else: + return oadrUpdatedReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_oadrCancelReport(self): return self.oadrCancelReport + def set_oadrCancelReport(self, oadrCancelReport): self.oadrCancelReport = oadrCancelReport + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.oadrCancelReport is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrUpdatedReportType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrUpdatedReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrUpdatedReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrUpdatedReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrUpdatedReportType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrUpdatedReportType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.oadrCancelReport is not None: + self.oadrCancelReport.export(outfile, level, namespace_='oadr:', name_='oadrCancelReport', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'oadrCancelReport': + obj_ = oadrCancelReportType.factory() + obj_.build(child_) + self.oadrCancelReport = obj_ + obj_.original_tagname_ = 'oadrCancelReport' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrUpdatedReportType + + +class oadrCancelPartyRegistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, registrationID=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + self.registrationID = registrationID + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCancelPartyRegistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCancelPartyRegistrationType.subclass: + return oadrCancelPartyRegistrationType.subclass(*args_, **kwargs_) + else: + return oadrCancelPartyRegistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_registrationID(self): return self.registrationID + def set_registrationID(self, registrationID): self.registrationID = registrationID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.registrationID is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCancelPartyRegistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCancelPartyRegistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCancelPartyRegistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCancelPartyRegistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCancelPartyRegistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCancelPartyRegistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.registrationID is not None: + self.registrationID.export(outfile, level, namespace_='ei:', name_='registrationID', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'registrationID': + obj_ = registrationID.factory() + obj_.build(child_) + self.registrationID = obj_ + obj_.original_tagname_ = 'registrationID' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCancelPartyRegistrationType + + +class oadrCanceledPartyRegistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, registrationID=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.registrationID = registrationID + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCanceledPartyRegistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCanceledPartyRegistrationType.subclass: + return oadrCanceledPartyRegistrationType.subclass(*args_, **kwargs_) + else: + return oadrCanceledPartyRegistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_registrationID(self): return self.registrationID + def set_registrationID(self, registrationID): self.registrationID = registrationID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.registrationID is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCanceledPartyRegistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCanceledPartyRegistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCanceledPartyRegistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCanceledPartyRegistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCanceledPartyRegistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCanceledPartyRegistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.registrationID is not None: + self.registrationID.export(outfile, level, namespace_='ei:', name_='registrationID', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'registrationID': + obj_ = registrationID.factory() + obj_.build(child_) + self.registrationID = obj_ + obj_.original_tagname_ = 'registrationID' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrCanceledPartyRegistrationType + + +class oadrCreatePartyRegistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None, registrationID=None, venID=None, oadrProfileName=None, oadrTransportName=None, oadrTransportAddress=None, oadrReportOnly=None, oadrXmlSignature=None, oadrVenName=None, oadrHttpPullModel=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + self.registrationID = registrationID + self.venID = venID + self.oadrProfileName = oadrProfileName + self.oadrTransportName = oadrTransportName + self.oadrTransportAddress = oadrTransportAddress + self.oadrReportOnly = oadrReportOnly + self.oadrXmlSignature = oadrXmlSignature + self.oadrVenName = oadrVenName + self.oadrHttpPullModel = oadrHttpPullModel + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreatePartyRegistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreatePartyRegistrationType.subclass: + return oadrCreatePartyRegistrationType.subclass(*args_, **kwargs_) + else: + return oadrCreatePartyRegistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_registrationID(self): return self.registrationID + def set_registrationID(self, registrationID): self.registrationID = registrationID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_oadrProfileName(self): return self.oadrProfileName + def set_oadrProfileName(self, oadrProfileName): self.oadrProfileName = oadrProfileName + def get_oadrTransportName(self): return self.oadrTransportName + def set_oadrTransportName(self, oadrTransportName): self.oadrTransportName = oadrTransportName + def get_oadrTransportAddress(self): return self.oadrTransportAddress + def set_oadrTransportAddress(self, oadrTransportAddress): self.oadrTransportAddress = oadrTransportAddress + def get_oadrReportOnly(self): return self.oadrReportOnly + def set_oadrReportOnly(self, oadrReportOnly): self.oadrReportOnly = oadrReportOnly + def get_oadrXmlSignature(self): return self.oadrXmlSignature + def set_oadrXmlSignature(self, oadrXmlSignature): self.oadrXmlSignature = oadrXmlSignature + def get_oadrVenName(self): return self.oadrVenName + def set_oadrVenName(self, oadrVenName): self.oadrVenName = oadrVenName + def get_oadrHttpPullModel(self): return self.oadrHttpPullModel + def set_oadrHttpPullModel(self, oadrHttpPullModel): self.oadrHttpPullModel = oadrHttpPullModel + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None or + self.registrationID is not None or + self.venID is not None or + self.oadrProfileName is not None or + self.oadrTransportName is not None or + self.oadrTransportAddress is not None or + self.oadrReportOnly is not None or + self.oadrXmlSignature is not None or + self.oadrVenName is not None or + self.oadrHttpPullModel is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreatePartyRegistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreatePartyRegistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreatePartyRegistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreatePartyRegistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreatePartyRegistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreatePartyRegistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.registrationID is not None: + self.registrationID.export(outfile, level, namespace_='ei:', name_='registrationID', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + if self.oadrProfileName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrProfileName), input_name='oadrProfileName')), eol_)) + if self.oadrTransportName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrTransportName), input_name='oadrTransportName')), eol_)) + if self.oadrTransportAddress is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrTransportAddress), input_name='oadrTransportAddress')), eol_)) + if self.oadrReportOnly is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrReportOnly, input_name='oadrReportOnly'), eol_)) + if self.oadrXmlSignature is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrXmlSignature, input_name='oadrXmlSignature'), eol_)) + if self.oadrVenName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrVenName), input_name='oadrVenName')), eol_)) + if self.oadrHttpPullModel is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrHttpPullModel, input_name='oadrHttpPullModel'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'registrationID': + obj_ = registrationID.factory() + obj_.build(child_) + self.registrationID = obj_ + obj_.original_tagname_ = 'registrationID' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ + elif nodeName_ == 'oadrProfileName': + oadrProfileName_ = child_.text + if oadrProfileName_: + oadrProfileName_ = re_.sub(String_cleanup_pat_, " ", oadrProfileName_).strip() + else: + oadrProfileName_ = "" + oadrProfileName_ = self.gds_validate_string(oadrProfileName_, node, 'oadrProfileName') + self.oadrProfileName = oadrProfileName_ + elif nodeName_ == 'oadrTransportName': + oadrTransportName_ = child_.text + if oadrTransportName_: + oadrTransportName_ = re_.sub(String_cleanup_pat_, " ", oadrTransportName_).strip() + else: + oadrTransportName_ = "" + oadrTransportName_ = self.gds_validate_string(oadrTransportName_, node, 'oadrTransportName') + self.oadrTransportName = oadrTransportName_ + elif nodeName_ == 'oadrTransportAddress': + oadrTransportAddress_ = child_.text + oadrTransportAddress_ = self.gds_validate_string(oadrTransportAddress_, node, 'oadrTransportAddress') + self.oadrTransportAddress = oadrTransportAddress_ + elif nodeName_ == 'oadrReportOnly': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrReportOnly') + self.oadrReportOnly = ival_ + elif nodeName_ == 'oadrXmlSignature': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrXmlSignature') + self.oadrXmlSignature = ival_ + elif nodeName_ == 'oadrVenName': + oadrVenName_ = child_.text + oadrVenName_ = self.gds_validate_string(oadrVenName_, node, 'oadrVenName') + self.oadrVenName = oadrVenName_ + elif nodeName_ == 'oadrHttpPullModel': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrHttpPullModel') + self.oadrHttpPullModel = ival_ +# end class oadrCreatePartyRegistrationType + + +class oadrCreatedPartyRegistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, eiResponse=None, registrationID=None, venID=None, vtnID=None, oadrProfiles=None, oadrRequestedOadrPollFreq=None, oadrServiceSpecificInfo=None, oadrExtensions=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.eiResponse = eiResponse + self.registrationID = registrationID + self.venID = venID + self.vtnID = vtnID + self.oadrProfiles = oadrProfiles + self.oadrRequestedOadrPollFreq = oadrRequestedOadrPollFreq + self.oadrServiceSpecificInfo = oadrServiceSpecificInfo + self.oadrExtensions = oadrExtensions + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreatedPartyRegistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreatedPartyRegistrationType.subclass: + return oadrCreatedPartyRegistrationType.subclass(*args_, **kwargs_) + else: + return oadrCreatedPartyRegistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_registrationID(self): return self.registrationID + def set_registrationID(self, registrationID): self.registrationID = registrationID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_vtnID(self): return self.vtnID + def set_vtnID(self, vtnID): self.vtnID = vtnID + def get_oadrProfiles(self): return self.oadrProfiles + def set_oadrProfiles(self, oadrProfiles): self.oadrProfiles = oadrProfiles + def get_oadrRequestedOadrPollFreq(self): return self.oadrRequestedOadrPollFreq + def set_oadrRequestedOadrPollFreq(self, oadrRequestedOadrPollFreq): self.oadrRequestedOadrPollFreq = oadrRequestedOadrPollFreq + def get_oadrServiceSpecificInfo(self): return self.oadrServiceSpecificInfo + def set_oadrServiceSpecificInfo(self, oadrServiceSpecificInfo): self.oadrServiceSpecificInfo = oadrServiceSpecificInfo + def get_oadrExtensions(self): return self.oadrExtensions + def set_oadrExtensions(self, oadrExtensions): self.oadrExtensions = oadrExtensions + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.eiResponse is not None or + self.registrationID is not None or + self.venID is not None or + self.vtnID is not None or + self.oadrProfiles is not None or + self.oadrRequestedOadrPollFreq is not None or + self.oadrServiceSpecificInfo is not None or + self.oadrExtensions is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreatedPartyRegistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreatedPartyRegistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreatedPartyRegistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreatedPartyRegistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreatedPartyRegistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreatedPartyRegistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.registrationID is not None: + self.registrationID.export(outfile, level, namespace_='ei:', name_='registrationID', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + if self.vtnID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.vtnID), input_name='vtnID')), eol_)) + if self.oadrProfiles is not None: + self.oadrProfiles.export(outfile, level, namespace_='oadr:', name_='oadrProfiles', pretty_print=pretty_print) + if self.oadrRequestedOadrPollFreq is not None: + self.oadrRequestedOadrPollFreq.export(outfile, level, namespace_='oadr:', name_='oadrRequestedOadrPollFreq', pretty_print=pretty_print) + if self.oadrServiceSpecificInfo is not None: + self.oadrServiceSpecificInfo.export(outfile, level, namespace_='oadr:', name_='oadrServiceSpecificInfo', pretty_print=pretty_print) + if self.oadrExtensions is not None: + self.oadrExtensions.export(outfile, level, namespace_, name_='oadrExtensions', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'registrationID': + obj_ = registrationID.factory() + obj_.build(child_) + self.registrationID = obj_ + obj_.original_tagname_ = 'registrationID' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ + elif nodeName_ == 'vtnID': + vtnID_ = child_.text + vtnID_ = self.gds_validate_string(vtnID_, node, 'vtnID') + self.vtnID = vtnID_ + elif nodeName_ == 'oadrProfiles': + obj_ = oadrProfiles.factory() + obj_.build(child_) + self.oadrProfiles = obj_ + obj_.original_tagname_ = 'oadrProfiles' + elif nodeName_ == 'oadrRequestedOadrPollFreq': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.oadrRequestedOadrPollFreq = obj_ + obj_.original_tagname_ = 'oadrRequestedOadrPollFreq' + elif nodeName_ == 'oadrServiceSpecificInfo': + obj_ = oadrServiceSpecificInfo.factory() + obj_.build(child_) + self.oadrServiceSpecificInfo = obj_ + obj_.original_tagname_ = 'oadrServiceSpecificInfo' + elif nodeName_ == 'oadrExtensions': + obj_ = oadrExtensionsType.factory() + obj_.build(child_) + self.oadrExtensions = obj_ + obj_.original_tagname_ = 'oadrExtensions' +# end class oadrCreatedPartyRegistrationType + + +class oadrRequestReregistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrRequestReregistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrRequestReregistrationType.subclass: + return oadrRequestReregistrationType.subclass(*args_, **kwargs_) + else: + return oadrRequestReregistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrRequestReregistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrRequestReregistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrRequestReregistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrRequestReregistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrRequestReregistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrRequestReregistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrRequestReregistrationType + + +class oadrQueryRegistrationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, requestID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.requestID = requestID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrQueryRegistrationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrQueryRegistrationType.subclass: + return oadrQueryRegistrationType.subclass(*args_, **kwargs_) + else: + return oadrQueryRegistrationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.requestID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrQueryRegistrationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrQueryRegistrationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrQueryRegistrationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrQueryRegistrationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrQueryRegistrationType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrQueryRegistrationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ +# end class oadrQueryRegistrationType + + +class oadrPollType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, schemaVersion=None, venID=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrPollType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrPollType.subclass: + return oadrPollType.subclass(*args_, **kwargs_) + else: + return oadrPollType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def hasContent_(self): + if ( + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrPollType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrPollType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrPollType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrPollType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrPollType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrPollType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class oadrPollType + + +class oadrProfiles(GeneratedsSuper): + """OpenADR profiles supported by the implementation""" + subclass = None + superclass = None + def __init__(self, oadrProfile=None): + self.original_tagname_ = None + if oadrProfile is None: + self.oadrProfile = [] + else: + self.oadrProfile = oadrProfile + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrProfiles) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrProfiles.subclass: + return oadrProfiles.subclass(*args_, **kwargs_) + else: + return oadrProfiles(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrProfile(self): return self.oadrProfile + def set_oadrProfile(self, oadrProfile): self.oadrProfile = oadrProfile + def add_oadrProfile(self, value): self.oadrProfile.append(value) + def insert_oadrProfile_at(self, index, value): self.oadrProfile.insert(index, value) + def replace_oadrProfile_at(self, index, value): self.oadrProfile[index] = value + def hasContent_(self): + if ( + self.oadrProfile + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrProfiles', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrProfiles') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrProfiles') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrProfiles', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrProfiles'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrProfiles', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for oadrProfile_ in self.oadrProfile: + oadrProfile_.export(outfile, level, namespace_, name_='oadrProfile', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrProfile': + obj_ = oadrProfileType1.factory() + obj_.build(child_) + self.oadrProfile.append(obj_) + obj_.original_tagname_ = 'oadrProfile' +# end class oadrProfiles + + +class oadrTransports(GeneratedsSuper): + """OpenADR transports supported by implementation""" + subclass = None + superclass = None + def __init__(self, oadrTransport=None): + self.original_tagname_ = None + if oadrTransport is None: + self.oadrTransport = [] + else: + self.oadrTransport = oadrTransport + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrTransports) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrTransports.subclass: + return oadrTransports.subclass(*args_, **kwargs_) + else: + return oadrTransports(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrTransport(self): return self.oadrTransport + def set_oadrTransport(self, oadrTransport): self.oadrTransport = oadrTransport + def add_oadrTransport(self, value): self.oadrTransport.append(value) + def insert_oadrTransport_at(self, index, value): self.oadrTransport.insert(index, value) + def replace_oadrTransport_at(self, index, value): self.oadrTransport[index] = value + def hasContent_(self): + if ( + self.oadrTransport + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrTransports', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrTransports') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrTransports') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrTransports', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrTransports'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrTransports', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for oadrTransport_ in self.oadrTransport: + oadrTransport_.export(outfile, level, namespace_, name_='oadrTransport', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrTransport': + obj_ = oadrTransportType2.factory() + obj_.build(child_) + self.oadrTransport.append(obj_) + obj_.original_tagname_ = 'oadrTransport' +# end class oadrTransports + + +class oadrServiceSpecificInfo(GeneratedsSuper): + """Service specific registration information""" + subclass = None + superclass = None + def __init__(self, oadrService=None): + self.original_tagname_ = None + if oadrService is None: + self.oadrService = [] + else: + self.oadrService = oadrService + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrServiceSpecificInfo) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrServiceSpecificInfo.subclass: + return oadrServiceSpecificInfo.subclass(*args_, **kwargs_) + else: + return oadrServiceSpecificInfo(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrService(self): return self.oadrService + def set_oadrService(self, oadrService): self.oadrService = oadrService + def add_oadrService(self, value): self.oadrService.append(value) + def insert_oadrService_at(self, index, value): self.oadrService.insert(index, value) + def replace_oadrService_at(self, index, value): self.oadrService[index] = value + def hasContent_(self): + if ( + self.oadrService + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrServiceSpecificInfo', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrServiceSpecificInfo') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrServiceSpecificInfo') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrServiceSpecificInfo', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrServiceSpecificInfo'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrServiceSpecificInfo', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for oadrService_ in self.oadrService: + oadrService_.export(outfile, level, namespace_, name_='oadrService', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrService': + obj_ = oadrServiceType.factory() + obj_.build(child_) + self.oadrService.append(obj_) + obj_.original_tagname_ = 'oadrService' +# end class oadrServiceSpecificInfo + + +class oadrInfo(GeneratedsSuper): + """A key value pair of service specific registration information""" + subclass = None + superclass = None + def __init__(self, oadrKey=None, oadrValue=None): + self.original_tagname_ = None + self.oadrKey = oadrKey + self.oadrValue = oadrValue + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrInfo) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrInfo.subclass: + return oadrInfo.subclass(*args_, **kwargs_) + else: + return oadrInfo(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrKey(self): return self.oadrKey + def set_oadrKey(self, oadrKey): self.oadrKey = oadrKey + def get_oadrValue(self): return self.oadrValue + def set_oadrValue(self, oadrValue): self.oadrValue = oadrValue + def hasContent_(self): + if ( + self.oadrKey is not None or + self.oadrValue is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrInfo', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrInfo') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrInfo') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrInfo', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrInfo'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrInfo', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrKey is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrKey), input_name='oadrKey')), eol_)) + if self.oadrValue is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrValue), input_name='oadrValue')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrKey': + oadrKey_ = child_.text + oadrKey_ = self.gds_validate_string(oadrKey_, node, 'oadrKey') + self.oadrKey = oadrKey_ + elif nodeName_ == 'oadrValue': + oadrValue_ = child_.text + oadrValue_ = self.gds_validate_string(oadrValue_, node, 'oadrValue') + self.oadrValue = oadrValue_ +# end class oadrInfo + + +class oadrPendingReportsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, reportRequestID=None): + self.original_tagname_ = None + if reportRequestID is None: + self.reportRequestID = [] + else: + self.reportRequestID = reportRequestID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrPendingReportsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrPendingReportsType.subclass: + return oadrPendingReportsType.subclass(*args_, **kwargs_) + else: + return oadrPendingReportsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_reportRequestID(self): return self.reportRequestID + def set_reportRequestID(self, reportRequestID): self.reportRequestID = reportRequestID + def add_reportRequestID(self, value): self.reportRequestID.append(value) + def insert_reportRequestID_at(self, index, value): self.reportRequestID.insert(index, value) + def replace_reportRequestID_at(self, index, value): self.reportRequestID[index] = value + def hasContent_(self): + if ( + self.reportRequestID + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrPendingReportsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrPendingReportsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrPendingReportsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrPendingReportsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrPendingReportsType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrPendingReportsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for reportRequestID_ in self.reportRequestID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(reportRequestID_), input_name='reportRequestID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'reportRequestID': + reportRequestID_ = child_.text + reportRequestID_ = self.gds_validate_string(reportRequestID_, node, 'reportRequestID') + self.reportRequestID.append(reportRequestID_) +# end class oadrPendingReportsType + + +class oadrReportRequestType(GeneratedsSuper): + """This type is used to request an EiReport""" + subclass = None + superclass = None + def __init__(self, reportRequestID=None, reportSpecifier=None): + self.original_tagname_ = None + self.reportRequestID = reportRequestID + self.reportSpecifier = reportSpecifier + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrReportRequestType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrReportRequestType.subclass: + return oadrReportRequestType.subclass(*args_, **kwargs_) + else: + return oadrReportRequestType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_reportRequestID(self): return self.reportRequestID + def set_reportRequestID(self, reportRequestID): self.reportRequestID = reportRequestID + def get_reportSpecifier(self): return self.reportSpecifier + def set_reportSpecifier(self, reportSpecifier): self.reportSpecifier = reportSpecifier + def hasContent_(self): + if ( + self.reportRequestID is not None or + self.reportSpecifier is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrReportRequestType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrReportRequestType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportRequestType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrReportRequestType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrReportRequestType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrReportRequestType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.reportRequestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportRequestID), input_name='reportRequestID')), eol_)) + if self.reportSpecifier is not None: + self.reportSpecifier.export(outfile, level, namespace_='ei:', name_='reportSpecifier', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'reportRequestID': + reportRequestID_ = child_.text + reportRequestID_ = self.gds_validate_string(reportRequestID_, node, 'reportRequestID') + self.reportRequestID = reportRequestID_ + elif nodeName_ == 'reportSpecifier': + obj_ = ReportSpecifierType.factory() + obj_.build(child_) + self.reportSpecifier = obj_ + obj_.original_tagname_ = 'reportSpecifier' +# end class oadrReportRequestType + + +class oadrReportDescriptionType(GeneratedsSuper): + """Describes the subject and attributes of a report.""" + subclass = None + superclass = None + def __init__(self, rID=None, reportSubject=None, reportDataSource=None, reportType=None, itemBase=None, readingType=None, marketContext=None, oadrSamplingRate=None): + self.original_tagname_ = None + self.rID = rID + self.reportSubject = reportSubject + self.reportDataSource = reportDataSource + self.reportType = reportType + self.itemBase = itemBase + self.readingType = readingType + self.marketContext = marketContext + self.oadrSamplingRate = oadrSamplingRate + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrReportDescriptionType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrReportDescriptionType.subclass: + return oadrReportDescriptionType.subclass(*args_, **kwargs_) + else: + return oadrReportDescriptionType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_rID(self): return self.rID + def set_rID(self, rID): self.rID = rID + def get_reportSubject(self): return self.reportSubject + def set_reportSubject(self, reportSubject): self.reportSubject = reportSubject + def get_reportDataSource(self): return self.reportDataSource + def set_reportDataSource(self, reportDataSource): self.reportDataSource = reportDataSource + def get_reportType(self): return self.reportType + def set_reportType(self, reportType): self.reportType = reportType + def get_itemBase(self): return self.itemBase + def set_itemBase(self, itemBase): self.itemBase = itemBase + def get_readingType(self): return self.readingType + def set_readingType(self, readingType): self.readingType = readingType + def get_marketContext(self): return self.marketContext + def set_marketContext(self, marketContext): self.marketContext = marketContext + def get_oadrSamplingRate(self): return self.oadrSamplingRate + def set_oadrSamplingRate(self, oadrSamplingRate): self.oadrSamplingRate = oadrSamplingRate + def hasContent_(self): + if ( + self.rID is not None or + self.reportSubject is not None or + self.reportDataSource is not None or + self.reportType is not None or + self.itemBase is not None or + self.readingType is not None or + self.marketContext is not None or + self.oadrSamplingRate is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrReportDescriptionType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrReportDescriptionType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportDescriptionType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrReportDescriptionType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrReportDescriptionType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrReportDescriptionType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.rID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.rID), input_name='rID')), eol_)) + if self.reportSubject is not None: + self.reportSubject.export(outfile, level, namespace_='ei:', name_='reportSubject', pretty_print=pretty_print) + if self.reportDataSource is not None: + self.reportDataSource.export(outfile, level, namespace_='ei:', name_='reportDataSource', pretty_print=pretty_print) + if self.reportType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportType), input_name='reportType')), eol_)) + if self.itemBase is not None: + self.itemBase.export(outfile, level, namespace_, pretty_print=pretty_print) + if self.readingType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.readingType), input_name='readingType')), eol_)) + if self.marketContext is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.marketContext), input_name='marketContext')), eol_)) + if self.oadrSamplingRate is not None: + self.oadrSamplingRate.export(outfile, level, namespace_='oadr:', name_='oadrSamplingRate', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'rID': + rID_ = child_.text + rID_ = self.gds_validate_string(rID_, node, 'rID') + self.rID = rID_ + elif nodeName_ == 'reportSubject': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.reportSubject = obj_ + obj_.original_tagname_ = 'reportSubject' + elif nodeName_ == 'reportDataSource': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.reportDataSource = obj_ + obj_.original_tagname_ = 'reportDataSource' + elif nodeName_ == 'reportType': + reportType_ = child_.text + reportType_ = self.gds_validate_string(reportType_, node, 'reportType') + self.reportType = reportType_ + elif nodeName_ == 'itemBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'itemBase' + elif nodeName_ == 'customUnit': + obj_ = BaseUnitType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'customUnit' + elif nodeName_ == 'current': + obj_ = CurrentType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'current' + elif nodeName_ == 'currency': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currency' + elif nodeName_ == 'currencyPerKWh': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKWh' + elif nodeName_ == 'currencyPerKW': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKW' + elif nodeName_ == 'currencyPerThm': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerThm' + elif nodeName_ == 'frequency': + obj_ = FrequencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'frequency' + elif nodeName_ == 'Therm': + obj_ = ThermType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'Therm' + elif nodeName_ == 'temperature': + obj_ = temperatureType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'temperature' + elif nodeName_ == 'pulseCount': + obj_ = pulseCountType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'pulseCount' + elif nodeName_ == 'oadrGBDataDescription': + obj_ = oadrGBItemBase.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'oadrGBDataDescription' + elif nodeName_ == 'voltage': + obj_ = VoltageType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'voltage' + elif nodeName_ == 'energyItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'energyItem' + elif nodeName_ == 'powerItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'powerItem' + elif nodeName_ == 'energyApparent': + obj_ = EnergyApparentType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyApparent' + elif nodeName_ == 'energyReactive': + obj_ = EnergyReactiveType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReactive' + elif nodeName_ == 'energyReal': + obj_ = EnergyRealType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReal' + elif nodeName_ == 'powerApparent': + obj_ = PowerApparentType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerApparent' + elif nodeName_ == 'powerReactive': + obj_ = PowerReactiveType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReactive' + elif nodeName_ == 'powerReal': + obj_ = PowerRealType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReal' + elif nodeName_ == 'readingType': + readingType_ = child_.text + readingType_ = self.gds_validate_string(readingType_, node, 'readingType') + self.readingType = readingType_ + elif nodeName_ == 'marketContext': + marketContext_ = child_.text + marketContext_ = self.gds_validate_string(marketContext_, node, 'marketContext') + self.marketContext = marketContext_ + elif nodeName_ == 'oadrSamplingRate': + obj_ = oadrSamplingRateType.factory() + obj_.build(child_) + self.oadrSamplingRate = obj_ + obj_.original_tagname_ = 'oadrSamplingRate' +# end class oadrReportDescriptionType + + +class oadrSamplingRateType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrMinPeriod=None, oadrMaxPeriod=None, oadrOnChange=None): + self.original_tagname_ = None + self.oadrMinPeriod = oadrMinPeriod + self.validate_DurationValueType(self.oadrMinPeriod) + self.oadrMaxPeriod = oadrMaxPeriod + self.validate_DurationValueType(self.oadrMaxPeriod) + self.oadrOnChange = oadrOnChange + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrSamplingRateType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrSamplingRateType.subclass: + return oadrSamplingRateType.subclass(*args_, **kwargs_) + else: + return oadrSamplingRateType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrMinPeriod(self): return self.oadrMinPeriod + def set_oadrMinPeriod(self, oadrMinPeriod): self.oadrMinPeriod = oadrMinPeriod + def get_oadrMaxPeriod(self): return self.oadrMaxPeriod + def set_oadrMaxPeriod(self, oadrMaxPeriod): self.oadrMaxPeriod = oadrMaxPeriod + def get_oadrOnChange(self): return self.oadrOnChange + def set_oadrOnChange(self, oadrOnChange): self.oadrOnChange = oadrOnChange + def validate_DurationValueType(self, value): + # Validate type DurationValueType, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if not self.gds_validate_simple_patterns( + self.validate_DurationValueType_patterns_, value): + warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_DurationValueType_patterns_, )) + validate_DurationValueType_patterns_ = [['^(\\+$|^\\-)?P((\\d+Y)?(\\d+M)?(\\d+D)?T?(\\d+H)?(\\d+M)?(\\d+S)?)$|^(\\d+W)$']] + def hasContent_(self): + if ( + self.oadrMinPeriod is not None or + self.oadrMaxPeriod is not None or + self.oadrOnChange is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrSamplingRateType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrSamplingRateType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrSamplingRateType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrSamplingRateType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrSamplingRateType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrSamplingRateType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrMinPeriod is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrMinPeriod), input_name='oadrMinPeriod')), eol_)) + if self.oadrMaxPeriod is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrMaxPeriod), input_name='oadrMaxPeriod')), eol_)) + if self.oadrOnChange is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrOnChange, input_name='oadrOnChange'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrMinPeriod': + oadrMinPeriod_ = child_.text + oadrMinPeriod_ = self.gds_validate_string(oadrMinPeriod_, node, 'oadrMinPeriod') + self.oadrMinPeriod = oadrMinPeriod_ + # validate type DurationValueType + self.validate_DurationValueType(self.oadrMinPeriod) + elif nodeName_ == 'oadrMaxPeriod': + oadrMaxPeriod_ = child_.text + oadrMaxPeriod_ = self.gds_validate_string(oadrMaxPeriod_, node, 'oadrMaxPeriod') + self.oadrMaxPeriod = oadrMaxPeriod_ + # validate type DurationValueType + self.validate_DurationValueType(self.oadrMaxPeriod) + elif nodeName_ == 'oadrOnChange': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrOnChange') + self.oadrOnChange = ival_ +# end class oadrSamplingRateType + + +class oadrLoadControlStateType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrCapacity=None, oadrLevelOffset=None, oadrPercentOffset=None, oadrSetPoint=None): + self.original_tagname_ = None + self.oadrCapacity = oadrCapacity + self.oadrLevelOffset = oadrLevelOffset + self.oadrPercentOffset = oadrPercentOffset + self.oadrSetPoint = oadrSetPoint + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrLoadControlStateType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrLoadControlStateType.subclass: + return oadrLoadControlStateType.subclass(*args_, **kwargs_) + else: + return oadrLoadControlStateType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrCapacity(self): return self.oadrCapacity + def set_oadrCapacity(self, oadrCapacity): self.oadrCapacity = oadrCapacity + def get_oadrLevelOffset(self): return self.oadrLevelOffset + def set_oadrLevelOffset(self, oadrLevelOffset): self.oadrLevelOffset = oadrLevelOffset + def get_oadrPercentOffset(self): return self.oadrPercentOffset + def set_oadrPercentOffset(self, oadrPercentOffset): self.oadrPercentOffset = oadrPercentOffset + def get_oadrSetPoint(self): return self.oadrSetPoint + def set_oadrSetPoint(self, oadrSetPoint): self.oadrSetPoint = oadrSetPoint + def hasContent_(self): + if ( + self.oadrCapacity is not None or + self.oadrLevelOffset is not None or + self.oadrPercentOffset is not None or + self.oadrSetPoint is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrLoadControlStateType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrLoadControlStateType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrLoadControlStateType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrLoadControlStateType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrLoadControlStateType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrLoadControlStateType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrCapacity is not None: + self.oadrCapacity.export(outfile, level, namespace_, name_='oadrCapacity', pretty_print=pretty_print) + if self.oadrLevelOffset is not None: + self.oadrLevelOffset.export(outfile, level, namespace_, name_='oadrLevelOffset', pretty_print=pretty_print) + if self.oadrPercentOffset is not None: + self.oadrPercentOffset.export(outfile, level, namespace_, name_='oadrPercentOffset', pretty_print=pretty_print) + if self.oadrSetPoint is not None: + self.oadrSetPoint.export(outfile, level, namespace_, name_='oadrSetPoint', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrCapacity': + obj_ = oadrLoadControlStateTypeType.factory() + obj_.build(child_) + self.oadrCapacity = obj_ + obj_.original_tagname_ = 'oadrCapacity' + elif nodeName_ == 'oadrLevelOffset': + obj_ = oadrLoadControlStateTypeType.factory() + obj_.build(child_) + self.oadrLevelOffset = obj_ + obj_.original_tagname_ = 'oadrLevelOffset' + elif nodeName_ == 'oadrPercentOffset': + obj_ = oadrLoadControlStateTypeType.factory() + obj_.build(child_) + self.oadrPercentOffset = obj_ + obj_.original_tagname_ = 'oadrPercentOffset' + elif nodeName_ == 'oadrSetPoint': + obj_ = oadrLoadControlStateTypeType.factory() + obj_.build(child_) + self.oadrSetPoint = obj_ + obj_.original_tagname_ = 'oadrSetPoint' +# end class oadrLoadControlStateType + + +class oadrLoadControlStateTypeType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrMin=None, oadrMax=None, oadrCurrent=None, oadrNormal=None): + self.original_tagname_ = None + self.oadrMin = oadrMin + self.oadrMax = oadrMax + self.oadrCurrent = oadrCurrent + self.oadrNormal = oadrNormal + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrLoadControlStateTypeType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrLoadControlStateTypeType.subclass: + return oadrLoadControlStateTypeType.subclass(*args_, **kwargs_) + else: + return oadrLoadControlStateTypeType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrMin(self): return self.oadrMin + def set_oadrMin(self, oadrMin): self.oadrMin = oadrMin + def get_oadrMax(self): return self.oadrMax + def set_oadrMax(self, oadrMax): self.oadrMax = oadrMax + def get_oadrCurrent(self): return self.oadrCurrent + def set_oadrCurrent(self, oadrCurrent): self.oadrCurrent = oadrCurrent + def get_oadrNormal(self): return self.oadrNormal + def set_oadrNormal(self, oadrNormal): self.oadrNormal = oadrNormal + def hasContent_(self): + if ( + self.oadrMin is not None or + self.oadrMax is not None or + self.oadrCurrent is not None or + self.oadrNormal is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrLoadControlStateTypeType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrLoadControlStateTypeType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrLoadControlStateTypeType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrLoadControlStateTypeType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrLoadControlStateTypeType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrLoadControlStateTypeType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrMin is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.oadrMin, input_name='oadrMin'), eol_)) + if self.oadrMax is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.oadrMax, input_name='oadrMax'), eol_)) + if self.oadrCurrent is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.oadrCurrent, input_name='oadrCurrent'), eol_)) + if self.oadrNormal is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.oadrNormal, input_name='oadrNormal'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrMin': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'oadrMin') + self.oadrMin = fval_ + elif nodeName_ == 'oadrMax': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'oadrMax') + self.oadrMax = fval_ + elif nodeName_ == 'oadrCurrent': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'oadrCurrent') + self.oadrCurrent = fval_ + elif nodeName_ == 'oadrNormal': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'oadrNormal') + self.oadrNormal = fval_ +# end class oadrLoadControlStateTypeType + + +class intervals(GeneratedsSuper): + """Time intervals during which the DR event is active or report data is + available""" + subclass = None + superclass = None + def __init__(self, interval=None): + self.original_tagname_ = None + if interval is None: + self.interval = [] + else: + self.interval = interval + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, intervals) + if subclass is not None: + return subclass(*args_, **kwargs_) + if intervals.subclass: + return intervals.subclass(*args_, **kwargs_) + else: + return intervals(*args_, **kwargs_) + factory = staticmethod(factory) + def get_interval(self): return self.interval + def set_interval(self, interval): self.interval = interval + def add_interval(self, value): self.interval.append(value) + def insert_interval_at(self, index, value): self.interval.insert(index, value) + def replace_interval_at(self, index, value): self.interval[index] = value + def hasContent_(self): + if ( + self.interval + ): + return True + else: + return False + def export(self, outfile, level, namespace_='strm:', name_='intervals', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('intervals') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='intervals') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='strm:', name_='intervals', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='strm:', name_='intervals'): + pass + def exportChildren(self, outfile, level, namespace_='strm:', name_='intervals', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for interval_ in self.interval: + interval_.export(outfile, level, namespace_='ei:', name_='interval', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'interval': + obj_ = IntervalType.factory() + obj_.build(child_) + self.interval.append(obj_) + obj_.original_tagname_ = 'interval' +# end class intervals + + +class StreamPayloadBaseType(GeneratedsSuper): + """Abstract class to convey a payload for a stream. When a Stream is + transformed to or from a WS-Calendar Interval, the contents of + the Stream Payload defined element are transformed into the + contents of a WS-Calendar artifactBase.""" + subclass = None + superclass = None + def __init__(self, extensiontype_=None): + self.original_tagname_ = None + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, StreamPayloadBaseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if StreamPayloadBaseType.subclass: + return StreamPayloadBaseType.subclass(*args_, **kwargs_) + else: + return StreamPayloadBaseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='strm:', name_='StreamPayloadBaseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('StreamPayloadBaseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='StreamPayloadBaseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='strm:', name_='StreamPayloadBaseType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='strm:', name_='StreamPayloadBaseType'): + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + pass + def exportChildren(self, outfile, level, namespace_='strm:', name_='StreamPayloadBaseType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class StreamPayloadBaseType + + +class StreamBaseType(GeneratedsSuper): + """abstract base for communication of schedules for signals and + observations""" + subclass = None + superclass = None + def __init__(self, dtstart=None, duration=None, intervals=None, extensiontype_=None): + self.original_tagname_ = None + self.dtstart = dtstart + self.duration = duration + self.intervals = intervals + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, StreamBaseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if StreamBaseType.subclass: + return StreamBaseType.subclass(*args_, **kwargs_) + else: + return StreamBaseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_dtstart(self): return self.dtstart + def set_dtstart(self, dtstart): self.dtstart = dtstart + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def get_intervals(self): return self.intervals + def set_intervals(self, intervals): self.intervals = intervals + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + self.dtstart is not None or + self.duration is not None or + self.intervals is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='strm:', name_='StreamBaseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('StreamBaseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='StreamBaseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='strm:', name_='StreamBaseType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='strm:', name_='StreamBaseType'): + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + pass + def exportChildren(self, outfile, level, namespace_='strm:', name_='StreamBaseType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.dtstart is not None: + self.dtstart.export(outfile, level, namespace_='xcal:', name_='dtstart', pretty_print=pretty_print) + if self.duration is not None: + self.duration.export(outfile, level, namespace_='xcal:', name_='duration', pretty_print=pretty_print) + if self.intervals is not None: + self.intervals.export(outfile, level, namespace_='strm:', name_='intervals', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'dtstart': + obj_ = dtstart.factory() + obj_.build(child_) + self.dtstart = obj_ + obj_.original_tagname_ = 'dtstart' + elif nodeName_ == 'duration': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.duration = obj_ + obj_.original_tagname_ = 'duration' + elif nodeName_ == 'intervals': + obj_ = intervals.factory() + obj_.build(child_) + self.intervals = obj_ + obj_.original_tagname_ = 'intervals' +# end class StreamBaseType + + +class DurationPropType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, duration=None): + self.original_tagname_ = None + self.duration = duration + self.validate_DurationValueType(self.duration) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, DurationPropType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if DurationPropType.subclass: + return DurationPropType.subclass(*args_, **kwargs_) + else: + return DurationPropType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def validate_DurationValueType(self, value): + # Validate type DurationValueType, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if not self.gds_validate_simple_patterns( + self.validate_DurationValueType_patterns_, value): + warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_DurationValueType_patterns_, )) + validate_DurationValueType_patterns_ = [['^(\\+$|^\\-)?P((\\d+Y)?(\\d+M)?(\\d+D)?T?(\\d+H)?(\\d+M)?(\\d+S)?)$|^(\\d+W)$']] + def hasContent_(self): + if ( + self.duration is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='DurationPropType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('DurationPropType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='DurationPropType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='DurationPropType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='DurationPropType'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='DurationPropType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.duration is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.duration), input_name='duration')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'duration': + duration_ = child_.text + duration_ = self.gds_validate_string(duration_, node, 'duration') + self.duration = duration_ + # validate type DurationValueType + self.validate_DurationValueType(self.duration) +# end class DurationPropType + + +class dtstart(GeneratedsSuper): + """The starting time for the activity, data, or state change""" + subclass = None + superclass = None + def __init__(self, date_time=None): + self.original_tagname_ = None + if isinstance(date_time, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(date_time, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = date_time + self.date_time = initvalue_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, dtstart) + if subclass is not None: + return subclass(*args_, **kwargs_) + if dtstart.subclass: + return dtstart.subclass(*args_, **kwargs_) + else: + return dtstart(*args_, **kwargs_) + factory = staticmethod(factory) + def get_date_time(self): return self.date_time + def set_date_time(self, date_time): self.date_time = date_time + def hasContent_(self): + if ( + self.date_time is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='dtstart', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('dtstart') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='dtstart') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='dtstart', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='dtstart'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='dtstart', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.date_time is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.date_time, input_name='date-time'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'date-time': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.date_time = dval_ +# end class dtstart + + +class properties(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, dtstart=None, duration=None, tolerance=None, x_eiNotification=None, x_eiRampUp=None, x_eiRecovery=None): + self.original_tagname_ = None + self.dtstart = dtstart + self.duration = duration + self.tolerance = tolerance + self.x_eiNotification = x_eiNotification + self.x_eiRampUp = x_eiRampUp + self.x_eiRecovery = x_eiRecovery + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, properties) + if subclass is not None: + return subclass(*args_, **kwargs_) + if properties.subclass: + return properties.subclass(*args_, **kwargs_) + else: + return properties(*args_, **kwargs_) + factory = staticmethod(factory) + def get_dtstart(self): return self.dtstart + def set_dtstart(self, dtstart): self.dtstart = dtstart + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def get_tolerance(self): return self.tolerance + def set_tolerance(self, tolerance): self.tolerance = tolerance + def get_x_eiNotification(self): return self.x_eiNotification + def set_x_eiNotification(self, x_eiNotification): self.x_eiNotification = x_eiNotification + def get_x_eiRampUp(self): return self.x_eiRampUp + def set_x_eiRampUp(self, x_eiRampUp): self.x_eiRampUp = x_eiRampUp + def get_x_eiRecovery(self): return self.x_eiRecovery + def set_x_eiRecovery(self, x_eiRecovery): self.x_eiRecovery = x_eiRecovery + def hasContent_(self): + if ( + self.dtstart is not None or + self.duration is not None or + self.tolerance is not None or + self.x_eiNotification is not None or + self.x_eiRampUp is not None or + self.x_eiRecovery is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='properties', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('properties') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='properties') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='properties', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='properties'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='properties', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.dtstart is not None: + self.dtstart.export(outfile, level, namespace_='xcal:', name_='dtstart', pretty_print=pretty_print) + if self.duration is not None: + self.duration.export(outfile, level, namespace_='xcal:', name_='duration', pretty_print=pretty_print) + if self.tolerance is not None: + self.tolerance.export(outfile, level, namespace_, name_='tolerance', pretty_print=pretty_print) + if self.x_eiNotification is not None: + self.x_eiNotification.export(outfile, level, namespace_='ei:', name_='x-eiNotification', pretty_print=pretty_print) + if self.x_eiRampUp is not None: + self.x_eiRampUp.export(outfile, level, namespace_='ei:', name_='x-eiRampUp', pretty_print=pretty_print) + if self.x_eiRecovery is not None: + self.x_eiRecovery.export(outfile, level, namespace_='ei:', name_='x-eiRecovery', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'dtstart': + obj_ = dtstart.factory() + obj_.build(child_) + self.dtstart = obj_ + obj_.original_tagname_ = 'dtstart' + elif nodeName_ == 'duration': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.duration = obj_ + obj_.original_tagname_ = 'duration' + elif nodeName_ == 'tolerance': + obj_ = toleranceType.factory() + obj_.build(child_) + self.tolerance = obj_ + obj_.original_tagname_ = 'tolerance' + elif nodeName_ == 'x-eiNotification': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.x_eiNotification = obj_ + obj_.original_tagname_ = 'x-eiNotification' + elif nodeName_ == 'x-eiRampUp': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.x_eiRampUp = obj_ + obj_.original_tagname_ = 'x-eiRampUp' + elif nodeName_ == 'x-eiRecovery': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.x_eiRecovery = obj_ + obj_.original_tagname_ = 'x-eiRecovery' +# end class properties + + +class components(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self): + self.original_tagname_ = None + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, components) + if subclass is not None: + return subclass(*args_, **kwargs_) + if components.subclass: + return components.subclass(*args_, **kwargs_) + else: + return components(*args_, **kwargs_) + factory = staticmethod(factory) + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='components', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('components') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='components') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='components', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='components'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='components', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class components + + +class dtend(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, date_time=None): + self.original_tagname_ = None + if isinstance(date_time, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(date_time, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = date_time + self.date_time = initvalue_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, dtend) + if subclass is not None: + return subclass(*args_, **kwargs_) + if dtend.subclass: + return dtend.subclass(*args_, **kwargs_) + else: + return dtend(*args_, **kwargs_) + factory = staticmethod(factory) + def get_date_time(self): return self.date_time + def set_date_time(self, date_time): self.date_time = date_time + def hasContent_(self): + if ( + self.date_time is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='dtend', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('dtend') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='dtend') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='dtend', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='dtend'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='dtend', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.date_time is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.date_time, input_name='date-time'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'date-time': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.date_time = dval_ +# end class dtend + + +class VavailabilityType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, components=None): + self.original_tagname_ = None + self.components = components + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, VavailabilityType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if VavailabilityType.subclass: + return VavailabilityType.subclass(*args_, **kwargs_) + else: + return VavailabilityType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_components(self): return self.components + def set_components(self, components): self.components = components + def hasContent_(self): + if ( + self.components is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='VavailabilityType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('VavailabilityType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='VavailabilityType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='VavailabilityType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='VavailabilityType'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='VavailabilityType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.components is not None: + self.components.export(outfile, level, namespace_, name_='components', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'components': + obj_ = ArrayOfVavailabilityContainedComponents.factory() + obj_.build(child_) + self.components = obj_ + obj_.original_tagname_ = 'components' +# end class VavailabilityType + + +class ArrayOfVavailabilityContainedComponents(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, available=None): + self.original_tagname_ = None + if available is None: + self.available = [] + else: + self.available = available + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ArrayOfVavailabilityContainedComponents) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ArrayOfVavailabilityContainedComponents.subclass: + return ArrayOfVavailabilityContainedComponents.subclass(*args_, **kwargs_) + else: + return ArrayOfVavailabilityContainedComponents(*args_, **kwargs_) + factory = staticmethod(factory) + def get_available(self): return self.available + def set_available(self, available): self.available = available + def add_available(self, value): self.available.append(value) + def insert_available_at(self, index, value): self.available.insert(index, value) + def replace_available_at(self, index, value): self.available[index] = value + def hasContent_(self): + if ( + self.available + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='ArrayOfVavailabilityContainedComponents', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ArrayOfVavailabilityContainedComponents') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ArrayOfVavailabilityContainedComponents') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='ArrayOfVavailabilityContainedComponents', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='ArrayOfVavailabilityContainedComponents'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='ArrayOfVavailabilityContainedComponents', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for available_ in self.available: + available_.export(outfile, level, namespace_='xcal:', name_='available', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'available': + obj_ = AvailableType.factory() + obj_.build(child_) + self.available.append(obj_) + obj_.original_tagname_ = 'available' +# end class ArrayOfVavailabilityContainedComponents + + +class AvailableType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, properties=None): + self.original_tagname_ = None + self.properties = properties + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, AvailableType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if AvailableType.subclass: + return AvailableType.subclass(*args_, **kwargs_) + else: + return AvailableType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_properties(self): return self.properties + def set_properties(self, properties): self.properties = properties + def hasContent_(self): + if ( + self.properties is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='AvailableType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('AvailableType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='AvailableType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='AvailableType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='AvailableType'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='AvailableType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.properties is not None: + self.properties.export(outfile, level, namespace_='xcal:', name_='properties', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'properties': + obj_ = properties.factory() + obj_.build(child_) + self.properties = obj_ + obj_.original_tagname_ = 'properties' +# end class AvailableType + + +class WsCalendarIntervalType(GeneratedsSuper): + """An interval takes no sub-components.""" + subclass = None + superclass = None + def __init__(self, properties=None): + self.original_tagname_ = None + self.properties = properties + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, WsCalendarIntervalType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if WsCalendarIntervalType.subclass: + return WsCalendarIntervalType.subclass(*args_, **kwargs_) + else: + return WsCalendarIntervalType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_properties(self): return self.properties + def set_properties(self, properties): self.properties = properties + def hasContent_(self): + if ( + self.properties is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='xcal:', name_='WsCalendarIntervalType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('WsCalendarIntervalType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='WsCalendarIntervalType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='xcal:', name_='WsCalendarIntervalType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='xcal:', name_='WsCalendarIntervalType'): + pass + def exportChildren(self, outfile, level, namespace_='xcal:', name_='WsCalendarIntervalType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.properties is not None: + self.properties.export(outfile, level, namespace_='xcal:', name_='properties', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'properties': + obj_ = properties.factory() + obj_.build(child_) + self.properties = obj_ + obj_.original_tagname_ = 'properties' +# end class WsCalendarIntervalType + + +class QualifiedEventIDType(GeneratedsSuper): + """Fully qualified event ID includes the eventID and the + modificationNumber.""" + subclass = None + superclass = None + def __init__(self, eventID=None, modificationNumber=None): + self.original_tagname_ = None + self.eventID = eventID + self.modificationNumber = modificationNumber + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, QualifiedEventIDType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if QualifiedEventIDType.subclass: + return QualifiedEventIDType.subclass(*args_, **kwargs_) + else: + return QualifiedEventIDType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eventID(self): return self.eventID + def set_eventID(self, eventID): self.eventID = eventID + def get_modificationNumber(self): return self.modificationNumber + def set_modificationNumber(self, modificationNumber): self.modificationNumber = modificationNumber + def hasContent_(self): + if ( + self.eventID is not None or + self.modificationNumber is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='QualifiedEventIDType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('QualifiedEventIDType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='QualifiedEventIDType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='QualifiedEventIDType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='QualifiedEventIDType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='QualifiedEventIDType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eventID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.eventID), input_name='eventID')), eol_)) + if self.modificationNumber is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.modificationNumber, input_name='modificationNumber'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eventID': + eventID_ = child_.text + eventID_ = self.gds_validate_string(eventID_, node, 'eventID') + self.eventID = eventID_ + elif nodeName_ == 'modificationNumber': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'modificationNumber') + self.modificationNumber = ival_ +# end class QualifiedEventIDType + + +class IntervalType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, dtstart=None, duration=None, uid=None, streamPayloadBase=None): + self.original_tagname_ = None + self.dtstart = dtstart + self.duration = duration + self.uid = uid + if streamPayloadBase is None: + self.streamPayloadBase = [] + else: + self.streamPayloadBase = streamPayloadBase + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, IntervalType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if IntervalType.subclass: + return IntervalType.subclass(*args_, **kwargs_) + else: + return IntervalType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_dtstart(self): return self.dtstart + def set_dtstart(self, dtstart): self.dtstart = dtstart + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def get_uid(self): return self.uid + def set_uid(self, uid): self.uid = uid + def get_streamPayloadBase(self): return self.streamPayloadBase + def set_streamPayloadBase(self, streamPayloadBase): self.streamPayloadBase = streamPayloadBase + def add_streamPayloadBase(self, value): self.streamPayloadBase.append(value) + def insert_streamPayloadBase_at(self, index, value): self.streamPayloadBase.insert(index, value) + def replace_streamPayloadBase_at(self, index, value): self.streamPayloadBase[index] = value + def hasContent_(self): + if ( + self.dtstart is not None or + self.duration is not None or + self.uid is not None or + self.streamPayloadBase + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='IntervalType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('IntervalType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='IntervalType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='IntervalType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='IntervalType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='IntervalType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.dtstart is not None: + self.dtstart.export(outfile, level, namespace_='xcal:', name_='dtstart', pretty_print=pretty_print) + if self.duration is not None: + self.duration.export(outfile, level, namespace_='xcal:', name_='duration', pretty_print=pretty_print) + if self.uid is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.uid), input_name='uid')), eol_)) + for streamPayloadBase_ in self.streamPayloadBase: + streamPayloadBase_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'dtstart': + obj_ = dtstart.factory() + obj_.build(child_) + self.dtstart = obj_ + obj_.original_tagname_ = 'dtstart' + elif nodeName_ == 'duration': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.duration = obj_ + obj_.original_tagname_ = 'duration' + elif nodeName_ == 'uid': + uid_ = child_.text + uid_ = self.gds_validate_string(uid_, node, 'uid') + self.uid = uid_ + elif nodeName_ == 'refID': + obj_ = refID.factory() + obj_.build(child_) + self.uid = obj_ + obj_.original_tagname_ = 'refID' + elif nodeName_ == 'registrationID': + obj_ = registrationID.factory() + obj_.build(child_) + self.refID = obj_ + obj_.original_tagname_ = 'registrationID' + elif nodeName_ == 'streamPayloadBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.streamPayloadBase.append(obj_) + obj_.original_tagname_ = 'streamPayloadBase' + elif nodeName_ == 'oadrGBPayload': + obj_ = oadrGBStreamPayloadBase.factory() + obj_.build(child_) + self.streamPayloadBase.append(obj_) + obj_.original_tagname_ = 'oadrGBPayload' + elif nodeName_ == 'oadrReportPayload': + obj_ = oadrReportPayloadType.factory() + obj_.build(child_) + self.streamPayloadBase.append(obj_) + obj_.original_tagname_ = 'oadrReportPayload' + elif nodeName_ == 'signalPayload': + obj_ = signalPayloadType.factory() + obj_.build(child_) + self.streamPayloadBase.append(obj_) + obj_.original_tagname_ = 'signalPayload' +# end class IntervalType + + +class currentValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, payloadFloat=None): + self.original_tagname_ = None + self.payloadFloat = payloadFloat + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, currentValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if currentValueType.subclass: + return currentValueType.subclass(*args_, **kwargs_) + else: + return currentValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_payloadFloat(self): return self.payloadFloat + def set_payloadFloat(self, payloadFloat): self.payloadFloat = payloadFloat + def hasContent_(self): + if ( + self.payloadFloat is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='currentValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('currentValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='currentValueType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='currentValueType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='currentValueType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='currentValueType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.payloadFloat is not None: + self.payloadFloat.export(outfile, level, namespace_='ei:', name_='payloadFloat', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'payloadFloat': + obj_ = PayloadFloatType.factory() + obj_.build(child_) + self.payloadFloat = obj_ + obj_.original_tagname_ = 'payloadFloat' +# end class currentValueType + + +class PayloadBaseType(GeneratedsSuper): + """Base for information in signal / baseline / report payloads""" + subclass = None + superclass = None + def __init__(self, extensiontype_=None): + self.original_tagname_ = None + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PayloadBaseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PayloadBaseType.subclass: + return PayloadBaseType.subclass(*args_, **kwargs_) + else: + return PayloadBaseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='PayloadBaseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PayloadBaseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PayloadBaseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='PayloadBaseType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='PayloadBaseType'): + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='PayloadBaseType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class PayloadBaseType + + +class PayloadFloatType(PayloadBaseType): + """This is the payload for signals that require a quantity.""" + subclass = None + superclass = PayloadBaseType + def __init__(self, value=None): + self.original_tagname_ = None + super(PayloadFloatType, self).__init__() + self.value = value + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PayloadFloatType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PayloadFloatType.subclass: + return PayloadFloatType.subclass(*args_, **kwargs_) + else: + return PayloadFloatType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_value(self): return self.value + def set_value(self, value): self.value = value + def hasContent_(self): + if ( + self.value is not None or + super(PayloadFloatType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='PayloadFloatType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PayloadFloatType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PayloadFloatType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='PayloadFloatType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='PayloadFloatType'): + super(PayloadFloatType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='PayloadFloatType') + def exportChildren(self, outfile, level, namespace_='ei:', name_='PayloadFloatType', fromsubclass_=False, pretty_print=True): + super(PayloadFloatType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.value is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.value, input_name='value'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(PayloadFloatType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'value': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'value') + self.value = fval_ + super(PayloadFloatType, self).buildChildren(child_, node, nodeName_, True) +# end class PayloadFloatType + + +class EiResponseType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, responseCode=None, responseDescription=None, requestID=None): + self.original_tagname_ = None + self.responseCode = responseCode + self.responseDescription = responseDescription + self.requestID = requestID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EiResponseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EiResponseType.subclass: + return EiResponseType.subclass(*args_, **kwargs_) + else: + return EiResponseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_responseCode(self): return self.responseCode + def set_responseCode(self, responseCode): self.responseCode = responseCode + def get_responseDescription(self): return self.responseDescription + def set_responseDescription(self, responseDescription): self.responseDescription = responseDescription + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def hasContent_(self): + if ( + self.responseCode is not None or + self.responseDescription is not None or + self.requestID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='EiResponseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EiResponseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EiResponseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='EiResponseType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='EiResponseType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='EiResponseType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.responseCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.responseCode), input_name='responseCode')), eol_)) + if self.responseDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.responseDescription), input_name='responseDescription')), eol_)) + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'responseCode': + responseCode_ = child_.text + responseCode_ = self.gds_validate_string(responseCode_, node, 'responseCode') + self.responseCode = responseCode_ + elif nodeName_ == 'responseDescription': + responseDescription_ = child_.text + responseDescription_ = self.gds_validate_string(responseDescription_, node, 'responseDescription') + self.responseDescription = responseDescription_ + elif nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ +# end class EiResponseType + + +class eventResponses(GeneratedsSuper): + """optIn or optOut responses for received events""" + subclass = None + superclass = None + def __init__(self, eventResponse=None): + self.original_tagname_ = None + if eventResponse is None: + self.eventResponse = [] + else: + self.eventResponse = eventResponse + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eventResponses) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eventResponses.subclass: + return eventResponses.subclass(*args_, **kwargs_) + else: + return eventResponses(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eventResponse(self): return self.eventResponse + def set_eventResponse(self, eventResponse): self.eventResponse = eventResponse + def add_eventResponse(self, value): self.eventResponse.append(value) + def insert_eventResponse_at(self, index, value): self.eventResponse.insert(index, value) + def replace_eventResponse_at(self, index, value): self.eventResponse[index] = value + def hasContent_(self): + if ( + self.eventResponse + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eventResponses', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eventResponses') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eventResponses') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eventResponses', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eventResponses'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eventResponses', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for eventResponse_ in self.eventResponse: + eventResponse_.export(outfile, level, namespace_, name_='eventResponse', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eventResponse': + obj_ = eventResponseType.factory() + obj_.build(child_) + self.eventResponse.append(obj_) + obj_.original_tagname_ = 'eventResponse' +# end class eventResponses + + +class eiEventType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, eventDescriptor=None, eiActivePeriod=None, eiEventSignals=None, eiTarget=None): + self.original_tagname_ = None + self.eventDescriptor = eventDescriptor + self.eiActivePeriod = eiActivePeriod + self.eiEventSignals = eiEventSignals + self.eiTarget = eiTarget + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiEventType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiEventType.subclass: + return eiEventType.subclass(*args_, **kwargs_) + else: + return eiEventType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eventDescriptor(self): return self.eventDescriptor + def set_eventDescriptor(self, eventDescriptor): self.eventDescriptor = eventDescriptor + def get_eiActivePeriod(self): return self.eiActivePeriod + def set_eiActivePeriod(self, eiActivePeriod): self.eiActivePeriod = eiActivePeriod + def get_eiEventSignals(self): return self.eiEventSignals + def set_eiEventSignals(self, eiEventSignals): self.eiEventSignals = eiEventSignals + def get_eiTarget(self): return self.eiTarget + def set_eiTarget(self, eiTarget): self.eiTarget = eiTarget + def hasContent_(self): + if ( + self.eventDescriptor is not None or + self.eiActivePeriod is not None or + self.eiEventSignals is not None or + self.eiTarget is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eiEventType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiEventType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiEventType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eiEventType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eiEventType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eiEventType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eventDescriptor is not None: + self.eventDescriptor.export(outfile, level, namespace_='ei:', name_='eventDescriptor', pretty_print=pretty_print) + if self.eiActivePeriod is not None: + self.eiActivePeriod.export(outfile, level, namespace_='ei:', name_='eiActivePeriod', pretty_print=pretty_print) + if self.eiEventSignals is not None: + self.eiEventSignals.export(outfile, level, namespace_='ei:', name_='eiEventSignals', pretty_print=pretty_print) + if self.eiTarget is not None: + self.eiTarget.export(outfile, level, namespace_='ei:', name_='eiTarget', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eventDescriptor': + obj_ = eventDescriptorType.factory() + obj_.build(child_) + self.eventDescriptor = obj_ + obj_.original_tagname_ = 'eventDescriptor' + elif nodeName_ == 'eiActivePeriod': + obj_ = eiActivePeriodType.factory() + obj_.build(child_) + self.eiActivePeriod = obj_ + obj_.original_tagname_ = 'eiActivePeriod' + elif nodeName_ == 'eiEventSignals': + obj_ = eiEventSignalsType.factory() + obj_.build(child_) + self.eiEventSignals = obj_ + obj_.original_tagname_ = 'eiEventSignals' + elif nodeName_ == 'eiTarget': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.eiTarget = obj_ + obj_.original_tagname_ = 'eiTarget' +# end class eiEventType + + +class eiActivePeriodType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, properties=None, components=None): + self.original_tagname_ = None + self.properties = properties + self.components = components + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiActivePeriodType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiActivePeriodType.subclass: + return eiActivePeriodType.subclass(*args_, **kwargs_) + else: + return eiActivePeriodType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_properties(self): return self.properties + def set_properties(self, properties): self.properties = properties + def get_components(self): return self.components + def set_components(self, components): self.components = components + def hasContent_(self): + if ( + self.properties is not None or + self.components is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eiActivePeriodType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiActivePeriodType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiActivePeriodType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eiActivePeriodType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eiActivePeriodType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eiActivePeriodType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.properties is not None: + self.properties.export(outfile, level, namespace_='xcal:', name_='properties', pretty_print=pretty_print) + if self.components is not None: + self.components.export(outfile, level, namespace_='xcal:', name_='components', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'properties': + obj_ = properties.factory() + obj_.build(child_) + self.properties = obj_ + obj_.original_tagname_ = 'properties' + elif nodeName_ == 'components': + obj_ = components.factory() + obj_.build(child_) + self.components = obj_ + obj_.original_tagname_ = 'components' +# end class eiActivePeriodType + + +class ArrayofResponses(GeneratedsSuper): + """Collection of Responses. When a service operation regards multiple + referenceable items, each referenced item may have its own + response. Always accompanied by an overall Response Type.""" + subclass = None + superclass = None + def __init__(self, response=None): + self.original_tagname_ = None + if response is None: + self.response = [] + else: + self.response = response + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ArrayofResponses) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ArrayofResponses.subclass: + return ArrayofResponses.subclass(*args_, **kwargs_) + else: + return ArrayofResponses(*args_, **kwargs_) + factory = staticmethod(factory) + def get_response(self): return self.response + def set_response(self, response): self.response = response + def add_response(self, value): self.response.append(value) + def insert_response_at(self, index, value): self.response.insert(index, value) + def replace_response_at(self, index, value): self.response[index] = value + def hasContent_(self): + if ( + self.response + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='ArrayofResponses', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ArrayofResponses') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ArrayofResponses') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='ArrayofResponses', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='ArrayofResponses'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='ArrayofResponses', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for response_ in self.response: + response_.export(outfile, level, namespace_, name_='response', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'response': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.response.append(obj_) + obj_.original_tagname_ = 'response' +# end class ArrayofResponses + + +class eventDescriptorType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, eventID=None, modificationNumber=None, modificationDateTime=None, modificationReason=None, priority=None, eiMarketContext=None, createdDateTime=None, eventStatus=None, testEvent=None, vtnComment=None): + self.original_tagname_ = None + self.eventID = eventID + self.modificationNumber = modificationNumber + if isinstance(modificationDateTime, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(modificationDateTime, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = modificationDateTime + self.modificationDateTime = initvalue_ + self.modificationReason = modificationReason + self.priority = priority + self.eiMarketContext = eiMarketContext + if isinstance(createdDateTime, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = createdDateTime + self.createdDateTime = initvalue_ + self.eventStatus = eventStatus + self.testEvent = testEvent + self.vtnComment = vtnComment + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eventDescriptorType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eventDescriptorType.subclass: + return eventDescriptorType.subclass(*args_, **kwargs_) + else: + return eventDescriptorType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eventID(self): return self.eventID + def set_eventID(self, eventID): self.eventID = eventID + def get_modificationNumber(self): return self.modificationNumber + def set_modificationNumber(self, modificationNumber): self.modificationNumber = modificationNumber + def get_modificationDateTime(self): return self.modificationDateTime + def set_modificationDateTime(self, modificationDateTime): self.modificationDateTime = modificationDateTime + def get_modificationReason(self): return self.modificationReason + def set_modificationReason(self, modificationReason): self.modificationReason = modificationReason + def get_priority(self): return self.priority + def set_priority(self, priority): self.priority = priority + def get_eiMarketContext(self): return self.eiMarketContext + def set_eiMarketContext(self, eiMarketContext): self.eiMarketContext = eiMarketContext + def get_createdDateTime(self): return self.createdDateTime + def set_createdDateTime(self, createdDateTime): self.createdDateTime = createdDateTime + def get_eventStatus(self): return self.eventStatus + def set_eventStatus(self, eventStatus): self.eventStatus = eventStatus + def get_testEvent(self): return self.testEvent + def set_testEvent(self, testEvent): self.testEvent = testEvent + def get_vtnComment(self): return self.vtnComment + def set_vtnComment(self, vtnComment): self.vtnComment = vtnComment + def validate_DateTimeType(self, value): + # Validate type DateTimeType, a restriction on xs:dateTime. + if value is not None and Validate_simpletypes_: + if not self.gds_validate_simple_patterns( + self.validate_DateTimeType_patterns_, value): + warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_DateTimeType_patterns_, )) + validate_DateTimeType_patterns_ = [['^(\\-$|^\\+)?\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d*)?Z?$']] + def hasContent_(self): + if ( + self.eventID is not None or + self.modificationNumber is not None or + self.modificationDateTime is not None or + self.modificationReason is not None or + self.priority is not None or + self.eiMarketContext is not None or + self.createdDateTime is not None or + self.eventStatus is not None or + self.testEvent is not None or + self.vtnComment is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eventDescriptorType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eventDescriptorType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eventDescriptorType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eventDescriptorType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eventDescriptorType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eventDescriptorType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eventID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.eventID), input_name='eventID')), eol_)) + if self.modificationNumber is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.modificationNumber, input_name='modificationNumber'), eol_)) + if self.modificationDateTime is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.modificationDateTime, input_name='modificationDateTime'), eol_)) + if self.modificationReason is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.modificationReason), input_name='modificationReason')), eol_)) + if self.priority is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.priority, input_name='priority'), eol_)) + if self.eiMarketContext is not None: + self.eiMarketContext.export(outfile, level, namespace_, name_='eiMarketContext', pretty_print=pretty_print) + if self.createdDateTime is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.createdDateTime, input_name='createdDateTime'), eol_)) + if self.eventStatus is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.eventStatus), input_name='eventStatus')), eol_)) + if self.testEvent is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.testEvent), input_name='testEvent')), eol_)) + if self.vtnComment is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.vtnComment), input_name='vtnComment')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eventID': + eventID_ = child_.text + eventID_ = self.gds_validate_string(eventID_, node, 'eventID') + self.eventID = eventID_ + elif nodeName_ == 'modificationNumber': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'modificationNumber') + self.modificationNumber = ival_ + elif nodeName_ == 'modificationDateTime': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.modificationDateTime = dval_ + # validate type DateTimeType + self.validate_DateTimeType(self.modificationDateTime) + elif nodeName_ == 'modificationReason': + modificationReason_ = child_.text + modificationReason_ = self.gds_validate_string(modificationReason_, node, 'modificationReason') + self.modificationReason = modificationReason_ + elif nodeName_ == 'priority': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'priority') + self.priority = ival_ + elif nodeName_ == 'eiMarketContext': + obj_ = eiMarketContextType.factory() + obj_.build(child_) + self.eiMarketContext = obj_ + obj_.original_tagname_ = 'eiMarketContext' + elif nodeName_ == 'createdDateTime': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.createdDateTime = dval_ + elif nodeName_ == 'eventStatus': + eventStatus_ = child_.text + if eventStatus_: + eventStatus_ = re_.sub(String_cleanup_pat_, " ", eventStatus_).strip() + else: + eventStatus_ = "" + eventStatus_ = self.gds_validate_string(eventStatus_, node, 'eventStatus') + self.eventStatus = eventStatus_ + elif nodeName_ == 'testEvent': + testEvent_ = child_.text + testEvent_ = self.gds_validate_string(testEvent_, node, 'testEvent') + self.testEvent = testEvent_ + elif nodeName_ == 'vtnComment': + vtnComment_ = child_.text + vtnComment_ = self.gds_validate_string(vtnComment_, node, 'vtnComment') + self.vtnComment = vtnComment_ +# end class eventDescriptorType + + +class signalPayloadType(StreamPayloadBaseType): + subclass = None + superclass = StreamPayloadBaseType + def __init__(self, payloadBase=None): + self.original_tagname_ = None + super(signalPayloadType, self).__init__() + self.payloadBase = payloadBase + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, signalPayloadType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if signalPayloadType.subclass: + return signalPayloadType.subclass(*args_, **kwargs_) + else: + return signalPayloadType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_payloadBase(self): return self.payloadBase + def set_payloadBase(self, payloadBase): self.payloadBase = payloadBase + def hasContent_(self): + if ( + self.payloadBase is not None or + super(signalPayloadType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='signalPayloadType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('signalPayloadType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='signalPayloadType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='signalPayloadType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='signalPayloadType'): + super(signalPayloadType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='signalPayloadType') + def exportChildren(self, outfile, level, namespace_='ei:', name_='signalPayloadType', fromsubclass_=False, pretty_print=True): + super(signalPayloadType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.payloadBase is not None: + self.payloadBase.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(signalPayloadType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'payloadBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.payloadBase = obj_ + obj_.original_tagname_ = 'payloadBase' + elif nodeName_ == 'oadrPayloadResourceStatus': + obj_ = oadrPayloadResourceStatusType.factory() + obj_.build(child_) + self.payloadBase = obj_ + obj_.original_tagname_ = 'oadrPayloadResourceStatus' + elif nodeName_ == 'payloadFloat': + obj_ = PayloadFloatType.factory() + obj_.build(child_) + self.payloadBase = obj_ + obj_.original_tagname_ = 'payloadFloat' + super(signalPayloadType, self).buildChildren(child_, node, nodeName_, True) +# end class signalPayloadType + + +class EiTargetType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, aggregatedPnode=None, endDeviceAsset=None, meterAsset=None, pnode=None, serviceArea=None, serviceDeliveryPoint=None, serviceLocation=None, transportInterface=None, groupID=None, groupName=None, resourceID=None, venID=None, partyID=None): + self.original_tagname_ = None + if aggregatedPnode is None: + self.aggregatedPnode = [] + else: + self.aggregatedPnode = aggregatedPnode + if endDeviceAsset is None: + self.endDeviceAsset = [] + else: + self.endDeviceAsset = endDeviceAsset + if meterAsset is None: + self.meterAsset = [] + else: + self.meterAsset = meterAsset + if pnode is None: + self.pnode = [] + else: + self.pnode = pnode + if serviceArea is None: + self.serviceArea = [] + else: + self.serviceArea = serviceArea + if serviceDeliveryPoint is None: + self.serviceDeliveryPoint = [] + else: + self.serviceDeliveryPoint = serviceDeliveryPoint + if serviceLocation is None: + self.serviceLocation = [] + else: + self.serviceLocation = serviceLocation + if transportInterface is None: + self.transportInterface = [] + else: + self.transportInterface = transportInterface + if groupID is None: + self.groupID = [] + else: + self.groupID = groupID + if groupName is None: + self.groupName = [] + else: + self.groupName = groupName + if resourceID is None: + self.resourceID = [] + else: + self.resourceID = resourceID + if venID is None: + self.venID = [] + else: + self.venID = venID + if partyID is None: + self.partyID = [] + else: + self.partyID = partyID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EiTargetType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EiTargetType.subclass: + return EiTargetType.subclass(*args_, **kwargs_) + else: + return EiTargetType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_aggregatedPnode(self): return self.aggregatedPnode + def set_aggregatedPnode(self, aggregatedPnode): self.aggregatedPnode = aggregatedPnode + def add_aggregatedPnode(self, value): self.aggregatedPnode.append(value) + def insert_aggregatedPnode_at(self, index, value): self.aggregatedPnode.insert(index, value) + def replace_aggregatedPnode_at(self, index, value): self.aggregatedPnode[index] = value + def get_endDeviceAsset(self): return self.endDeviceAsset + def set_endDeviceAsset(self, endDeviceAsset): self.endDeviceAsset = endDeviceAsset + def add_endDeviceAsset(self, value): self.endDeviceAsset.append(value) + def insert_endDeviceAsset_at(self, index, value): self.endDeviceAsset.insert(index, value) + def replace_endDeviceAsset_at(self, index, value): self.endDeviceAsset[index] = value + def get_meterAsset(self): return self.meterAsset + def set_meterAsset(self, meterAsset): self.meterAsset = meterAsset + def add_meterAsset(self, value): self.meterAsset.append(value) + def insert_meterAsset_at(self, index, value): self.meterAsset.insert(index, value) + def replace_meterAsset_at(self, index, value): self.meterAsset[index] = value + def get_pnode(self): return self.pnode + def set_pnode(self, pnode): self.pnode = pnode + def add_pnode(self, value): self.pnode.append(value) + def insert_pnode_at(self, index, value): self.pnode.insert(index, value) + def replace_pnode_at(self, index, value): self.pnode[index] = value + def get_serviceArea(self): return self.serviceArea + def set_serviceArea(self, serviceArea): self.serviceArea = serviceArea + def add_serviceArea(self, value): self.serviceArea.append(value) + def insert_serviceArea_at(self, index, value): self.serviceArea.insert(index, value) + def replace_serviceArea_at(self, index, value): self.serviceArea[index] = value + def get_serviceDeliveryPoint(self): return self.serviceDeliveryPoint + def set_serviceDeliveryPoint(self, serviceDeliveryPoint): self.serviceDeliveryPoint = serviceDeliveryPoint + def add_serviceDeliveryPoint(self, value): self.serviceDeliveryPoint.append(value) + def insert_serviceDeliveryPoint_at(self, index, value): self.serviceDeliveryPoint.insert(index, value) + def replace_serviceDeliveryPoint_at(self, index, value): self.serviceDeliveryPoint[index] = value + def get_serviceLocation(self): return self.serviceLocation + def set_serviceLocation(self, serviceLocation): self.serviceLocation = serviceLocation + def add_serviceLocation(self, value): self.serviceLocation.append(value) + def insert_serviceLocation_at(self, index, value): self.serviceLocation.insert(index, value) + def replace_serviceLocation_at(self, index, value): self.serviceLocation[index] = value + def get_transportInterface(self): return self.transportInterface + def set_transportInterface(self, transportInterface): self.transportInterface = transportInterface + def add_transportInterface(self, value): self.transportInterface.append(value) + def insert_transportInterface_at(self, index, value): self.transportInterface.insert(index, value) + def replace_transportInterface_at(self, index, value): self.transportInterface[index] = value + def get_groupID(self): return self.groupID + def set_groupID(self, groupID): self.groupID = groupID + def add_groupID(self, value): self.groupID.append(value) + def insert_groupID_at(self, index, value): self.groupID.insert(index, value) + def replace_groupID_at(self, index, value): self.groupID[index] = value + def get_groupName(self): return self.groupName + def set_groupName(self, groupName): self.groupName = groupName + def add_groupName(self, value): self.groupName.append(value) + def insert_groupName_at(self, index, value): self.groupName.insert(index, value) + def replace_groupName_at(self, index, value): self.groupName[index] = value + def get_resourceID(self): return self.resourceID + def set_resourceID(self, resourceID): self.resourceID = resourceID + def add_resourceID(self, value): self.resourceID.append(value) + def insert_resourceID_at(self, index, value): self.resourceID.insert(index, value) + def replace_resourceID_at(self, index, value): self.resourceID[index] = value + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def add_venID(self, value): self.venID.append(value) + def insert_venID_at(self, index, value): self.venID.insert(index, value) + def replace_venID_at(self, index, value): self.venID[index] = value + def get_partyID(self): return self.partyID + def set_partyID(self, partyID): self.partyID = partyID + def add_partyID(self, value): self.partyID.append(value) + def insert_partyID_at(self, index, value): self.partyID.insert(index, value) + def replace_partyID_at(self, index, value): self.partyID[index] = value + def hasContent_(self): + if ( + self.aggregatedPnode or + self.endDeviceAsset or + self.meterAsset or + self.pnode or + self.serviceArea or + self.serviceDeliveryPoint or + self.serviceLocation or + self.transportInterface or + self.groupID or + self.groupName or + self.resourceID or + self.venID or + self.partyID + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='EiTargetType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EiTargetType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EiTargetType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='EiTargetType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='EiTargetType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='EiTargetType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for aggregatedPnode_ in self.aggregatedPnode: + aggregatedPnode_.export(outfile, level, namespace_='power:', name_='aggregatedPnode', pretty_print=pretty_print) + for endDeviceAsset_ in self.endDeviceAsset: + endDeviceAsset_.export(outfile, level, namespace_='power:', name_='endDeviceAsset', pretty_print=pretty_print) + for meterAsset_ in self.meterAsset: + meterAsset_.export(outfile, level, namespace_='power:', name_='meterAsset', pretty_print=pretty_print) + for pnode_ in self.pnode: + pnode_.export(outfile, level, namespace_='power:', name_='pnode', pretty_print=pretty_print) + for serviceArea_ in self.serviceArea: + serviceArea_.export(outfile, level, namespace_='emix:', name_='serviceArea', pretty_print=pretty_print) + for serviceDeliveryPoint_ in self.serviceDeliveryPoint: + serviceDeliveryPoint_.export(outfile, level, namespace_='power:', name_='serviceDeliveryPoint', pretty_print=pretty_print) + for serviceLocation_ in self.serviceLocation: + serviceLocation_.export(outfile, level, namespace_='power:', name_='serviceLocation', pretty_print=pretty_print) + for transportInterface_ in self.transportInterface: + transportInterface_.export(outfile, level, namespace_='power:', name_='transportInterface', pretty_print=pretty_print) + for groupID_ in self.groupID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(groupID_), input_name='groupID')), eol_)) + for groupName_ in self.groupName: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(groupName_), input_name='groupName')), eol_)) + for resourceID_ in self.resourceID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(resourceID_), input_name='resourceID')), eol_)) + for venID_ in self.venID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(venID_), input_name='venID')), eol_)) + for partyID_ in self.partyID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(partyID_), input_name='partyID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'aggregatedPnode': + obj_ = AggregatedPnodeType.factory() + obj_.build(child_) + self.aggregatedPnode.append(obj_) + obj_.original_tagname_ = 'aggregatedPnode' + elif nodeName_ == 'endDeviceAsset': + obj_ = EndDeviceAssetType.factory() + obj_.build(child_) + self.endDeviceAsset.append(obj_) + obj_.original_tagname_ = 'endDeviceAsset' + elif nodeName_ == 'meterAsset': + obj_ = MeterAssetType.factory() + obj_.build(child_) + self.meterAsset.append(obj_) + obj_.original_tagname_ = 'meterAsset' + elif nodeName_ == 'pnode': + obj_ = PnodeType.factory() + obj_.build(child_) + self.pnode.append(obj_) + obj_.original_tagname_ = 'pnode' + elif nodeName_ == 'serviceArea': + obj_ = ServiceAreaType.factory() + obj_.build(child_) + self.serviceArea.append(obj_) + obj_.original_tagname_ = 'serviceArea' + elif nodeName_ == 'serviceDeliveryPoint': + obj_ = ServiceDeliveryPointType.factory() + obj_.build(child_) + self.serviceDeliveryPoint.append(obj_) + obj_.original_tagname_ = 'serviceDeliveryPoint' + elif nodeName_ == 'serviceLocation': + obj_ = ServiceLocationType.factory() + obj_.build(child_) + self.serviceLocation.append(obj_) + obj_.original_tagname_ = 'serviceLocation' + elif nodeName_ == 'transportInterface': + obj_ = TransportInterfaceType.factory() + obj_.build(child_) + self.transportInterface.append(obj_) + obj_.original_tagname_ = 'transportInterface' + elif nodeName_ == 'groupID': + groupID_ = child_.text + groupID_ = self.gds_validate_string(groupID_, node, 'groupID') + self.groupID.append(groupID_) + elif nodeName_ == 'groupName': + groupName_ = child_.text + groupName_ = self.gds_validate_string(groupName_, node, 'groupName') + self.groupName.append(groupName_) + elif nodeName_ == 'resourceID': + resourceID_ = child_.text + resourceID_ = self.gds_validate_string(resourceID_, node, 'resourceID') + self.resourceID.append(resourceID_) + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID.append(venID_) + elif nodeName_ == 'partyID': + partyID_ = child_.text + partyID_ = self.gds_validate_string(partyID_, node, 'partyID') + self.partyID.append(partyID_) +# end class EiTargetType + + +class eiEventSignalType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, intervals=None, eiTarget=None, signalName=None, signalType=None, signalID=None, itemBase=None, currentValue=None): + self.original_tagname_ = None + self.intervals = intervals + self.eiTarget = eiTarget + self.signalName = signalName + self.signalType = signalType + self.signalID = signalID + self.itemBase = itemBase + self.currentValue = currentValue + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiEventSignalType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiEventSignalType.subclass: + return eiEventSignalType.subclass(*args_, **kwargs_) + else: + return eiEventSignalType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_intervals(self): return self.intervals + def set_intervals(self, intervals): self.intervals = intervals + def get_eiTarget(self): return self.eiTarget + def set_eiTarget(self, eiTarget): self.eiTarget = eiTarget + def get_signalName(self): return self.signalName + def set_signalName(self, signalName): self.signalName = signalName + def get_signalType(self): return self.signalType + def set_signalType(self, signalType): self.signalType = signalType + def get_signalID(self): return self.signalID + def set_signalID(self, signalID): self.signalID = signalID + def get_itemBase(self): return self.itemBase + def set_itemBase(self, itemBase): self.itemBase = itemBase + def get_currentValue(self): return self.currentValue + def set_currentValue(self, currentValue): self.currentValue = currentValue + def hasContent_(self): + if ( + self.intervals is not None or + self.eiTarget is not None or + self.signalName is not None or + self.signalType is not None or + self.signalID is not None or + self.itemBase is not None or + self.currentValue is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eiEventSignalType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiEventSignalType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiEventSignalType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eiEventSignalType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eiEventSignalType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eiEventSignalType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.intervals is not None: + self.intervals.export(outfile, level, namespace_='strm:', name_='intervals', pretty_print=pretty_print) + if self.eiTarget is not None: + self.eiTarget.export(outfile, level, namespace_='ei:', name_='eiTarget', pretty_print=pretty_print) + if self.signalName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.signalName), input_name='signalName')), eol_)) + if self.signalType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.signalType), input_name='signalType')), eol_)) + if self.signalID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.signalID), input_name='signalID')), eol_)) + if self.itemBase is not None: + self.itemBase.export(outfile, level, namespace_, pretty_print=pretty_print) + if self.currentValue is not None: + self.currentValue.export(outfile, level, namespace_='ei:', name_='currentValue', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'intervals': + obj_ = intervals.factory() + obj_.build(child_) + self.intervals = obj_ + obj_.original_tagname_ = 'intervals' + elif nodeName_ == 'eiTarget': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.eiTarget = obj_ + obj_.original_tagname_ = 'eiTarget' + elif nodeName_ == 'signalName': + signalName_ = child_.text + signalName_ = self.gds_validate_string(signalName_, node, 'signalName') + self.signalName = signalName_ + elif nodeName_ == 'signalType': + signalType_ = child_.text + if signalType_: + signalType_ = re_.sub(String_cleanup_pat_, " ", signalType_).strip() + else: + signalType_ = "" + signalType_ = self.gds_validate_string(signalType_, node, 'signalType') + self.signalType = signalType_ + elif nodeName_ == 'signalID': + signalID_ = child_.text + signalID_ = self.gds_validate_string(signalID_, node, 'signalID') + self.signalID = signalID_ + elif nodeName_ == 'itemBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'itemBase' + elif nodeName_ == 'customUnit': + obj_ = BaseUnitType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'customUnit' + elif nodeName_ == 'current': + obj_ = CurrentType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'current' + elif nodeName_ == 'currency': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currency' + elif nodeName_ == 'currencyPerKWh': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKWh' + elif nodeName_ == 'currencyPerKW': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKW' + elif nodeName_ == 'currencyPerThm': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerThm' + elif nodeName_ == 'frequency': + obj_ = FrequencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'frequency' + elif nodeName_ == 'Therm': + obj_ = ThermType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'Therm' + elif nodeName_ == 'temperature': + obj_ = temperatureType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'temperature' + elif nodeName_ == 'pulseCount': + obj_ = pulseCountType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'pulseCount' + elif nodeName_ == 'oadrGBDataDescription': + obj_ = oadrGBItemBase.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'oadrGBDataDescription' + elif nodeName_ == 'voltage': + obj_ = VoltageType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'voltage' + elif nodeName_ == 'energyItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'energyItem' + elif nodeName_ == 'powerItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'powerItem' + elif nodeName_ == 'energyApparent': + obj_ = EnergyApparentType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyApparent' + elif nodeName_ == 'energyReactive': + obj_ = EnergyReactiveType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReactive' + elif nodeName_ == 'energyReal': + obj_ = EnergyRealType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReal' + elif nodeName_ == 'powerApparent': + obj_ = PowerApparentType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerApparent' + elif nodeName_ == 'powerReactive': + obj_ = PowerReactiveType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReactive' + elif nodeName_ == 'powerReal': + obj_ = PowerRealType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReal' + elif nodeName_ == 'currentValue': + obj_ = currentValueType.factory() + obj_.build(child_) + self.currentValue = obj_ + obj_.original_tagname_ = 'currentValue' +# end class eiEventSignalType + + +class eiEventSignalsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, eiEventSignal=None, eiEventBaseline=None): + self.original_tagname_ = None + if eiEventSignal is None: + self.eiEventSignal = [] + else: + self.eiEventSignal = eiEventSignal + self.eiEventBaseline = eiEventBaseline + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiEventSignalsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiEventSignalsType.subclass: + return eiEventSignalsType.subclass(*args_, **kwargs_) + else: + return eiEventSignalsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiEventSignal(self): return self.eiEventSignal + def set_eiEventSignal(self, eiEventSignal): self.eiEventSignal = eiEventSignal + def add_eiEventSignal(self, value): self.eiEventSignal.append(value) + def insert_eiEventSignal_at(self, index, value): self.eiEventSignal.insert(index, value) + def replace_eiEventSignal_at(self, index, value): self.eiEventSignal[index] = value + def get_eiEventBaseline(self): return self.eiEventBaseline + def set_eiEventBaseline(self, eiEventBaseline): self.eiEventBaseline = eiEventBaseline + def hasContent_(self): + if ( + self.eiEventSignal or + self.eiEventBaseline is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eiEventSignalsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiEventSignalsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiEventSignalsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eiEventSignalsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eiEventSignalsType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eiEventSignalsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for eiEventSignal_ in self.eiEventSignal: + eiEventSignal_.export(outfile, level, namespace_='ei:', name_='eiEventSignal', pretty_print=pretty_print) + if self.eiEventBaseline is not None: + self.eiEventBaseline.export(outfile, level, namespace_='ei:', name_='eiEventBaseline', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiEventSignal': + obj_ = eiEventSignalType.factory() + obj_.build(child_) + self.eiEventSignal.append(obj_) + obj_.original_tagname_ = 'eiEventSignal' + elif nodeName_ == 'eiEventBaseline': + obj_ = eiEventBaselineType.factory() + obj_.build(child_) + self.eiEventBaseline = obj_ + obj_.original_tagname_ = 'eiEventBaseline' +# end class eiEventSignalsType + + +class eiEventBaselineType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, dtstart=None, duration=None, intervals=None, baselineID=None, resourceID=None, baselineName=None, itemBase=None): + self.original_tagname_ = None + self.dtstart = dtstart + self.duration = duration + self.intervals = intervals + self.baselineID = baselineID + if resourceID is None: + self.resourceID = [] + else: + self.resourceID = resourceID + self.baselineName = baselineName + self.itemBase = itemBase + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiEventBaselineType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiEventBaselineType.subclass: + return eiEventBaselineType.subclass(*args_, **kwargs_) + else: + return eiEventBaselineType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_dtstart(self): return self.dtstart + def set_dtstart(self, dtstart): self.dtstart = dtstart + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def get_intervals(self): return self.intervals + def set_intervals(self, intervals): self.intervals = intervals + def get_baselineID(self): return self.baselineID + def set_baselineID(self, baselineID): self.baselineID = baselineID + def get_resourceID(self): return self.resourceID + def set_resourceID(self, resourceID): self.resourceID = resourceID + def add_resourceID(self, value): self.resourceID.append(value) + def insert_resourceID_at(self, index, value): self.resourceID.insert(index, value) + def replace_resourceID_at(self, index, value): self.resourceID[index] = value + def get_baselineName(self): return self.baselineName + def set_baselineName(self, baselineName): self.baselineName = baselineName + def get_itemBase(self): return self.itemBase + def set_itemBase(self, itemBase): self.itemBase = itemBase + def hasContent_(self): + if ( + self.dtstart is not None or + self.duration is not None or + self.intervals is not None or + self.baselineID is not None or + self.resourceID or + self.baselineName is not None or + self.itemBase is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='eiEventBaselineType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiEventBaselineType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiEventBaselineType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='eiEventBaselineType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='eiEventBaselineType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='eiEventBaselineType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.dtstart is not None: + self.dtstart.export(outfile, level, namespace_='xcal:', name_='dtstart', pretty_print=pretty_print) + if self.duration is not None: + self.duration.export(outfile, level, namespace_='xcal:', name_='duration', pretty_print=pretty_print) + if self.intervals is not None: + self.intervals.export(outfile, level, namespace_='strm:', name_='intervals', pretty_print=pretty_print) + if self.baselineID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.baselineID), input_name='baselineID')), eol_)) + for resourceID_ in self.resourceID: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(resourceID_), input_name='resourceID')), eol_)) + if self.baselineName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.baselineName), input_name='baselineName')), eol_)) + if self.itemBase is not None: + self.itemBase.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'dtstart': + obj_ = dtstart.factory() + obj_.build(child_) + self.dtstart = obj_ + obj_.original_tagname_ = 'dtstart' + elif nodeName_ == 'duration': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.duration = obj_ + obj_.original_tagname_ = 'duration' + elif nodeName_ == 'intervals': + obj_ = intervals.factory() + obj_.build(child_) + self.intervals = obj_ + obj_.original_tagname_ = 'intervals' + elif nodeName_ == 'baselineID': + baselineID_ = child_.text + baselineID_ = self.gds_validate_string(baselineID_, node, 'baselineID') + self.baselineID = baselineID_ + elif nodeName_ == 'resourceID': + resourceID_ = child_.text + resourceID_ = self.gds_validate_string(resourceID_, node, 'resourceID') + self.resourceID.append(resourceID_) + elif nodeName_ == 'baselineName': + baselineName_ = child_.text + baselineName_ = self.gds_validate_string(baselineName_, node, 'baselineName') + self.baselineName = baselineName_ + elif nodeName_ == 'itemBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'itemBase' + elif nodeName_ == 'customUnit': + obj_ = BaseUnitType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'customUnit' + elif nodeName_ == 'current': + obj_ = CurrentType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'current' + elif nodeName_ == 'currency': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currency' + elif nodeName_ == 'currencyPerKWh': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKWh' + elif nodeName_ == 'currencyPerKW': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKW' + elif nodeName_ == 'currencyPerThm': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerThm' + elif nodeName_ == 'frequency': + obj_ = FrequencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'frequency' + elif nodeName_ == 'Therm': + obj_ = ThermType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'Therm' + elif nodeName_ == 'temperature': + obj_ = temperatureType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'temperature' + elif nodeName_ == 'pulseCount': + obj_ = pulseCountType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'pulseCount' + elif nodeName_ == 'oadrGBDataDescription': + obj_ = oadrGBItemBase.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'oadrGBDataDescription' + elif nodeName_ == 'voltage': + obj_ = VoltageType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'voltage' + elif nodeName_ == 'energyItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'energyItem' + elif nodeName_ == 'powerItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'powerItem' + elif nodeName_ == 'energyApparent': + obj_ = EnergyApparentType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyApparent' + elif nodeName_ == 'energyReactive': + obj_ = EnergyReactiveType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReactive' + elif nodeName_ == 'energyReal': + obj_ = EnergyRealType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReal' + elif nodeName_ == 'powerApparent': + obj_ = PowerApparentType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerApparent' + elif nodeName_ == 'powerReactive': + obj_ = PowerReactiveType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReactive' + elif nodeName_ == 'powerReal': + obj_ = PowerRealType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReal' +# end class eiEventBaselineType + + +class EiOptType(GeneratedsSuper): + """Opts are used by the VEN to temporarily override the pre-existing + agreement. For example, a VEN may opt in to events during the + evening, or opt out from events during the world series.""" + subclass = None + superclass = None + def __init__(self, schemaVersion=None, optID=None, optType=None, optReason=None, marketContext=None, venID=None, vavailability=None, createdDateTime=None, extensiontype_=None): + self.original_tagname_ = None + self.schemaVersion = _cast(None, schemaVersion) + self.optID = optID + self.optType = optType + self.optReason = optReason + self.marketContext = marketContext + self.venID = venID + self.vavailability = vavailability + if isinstance(createdDateTime, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = createdDateTime + self.createdDateTime = initvalue_ + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EiOptType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EiOptType.subclass: + return EiOptType.subclass(*args_, **kwargs_) + else: + return EiOptType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_optID(self): return self.optID + def set_optID(self, optID): self.optID = optID + def get_optType(self): return self.optType + def set_optType(self, optType): self.optType = optType + def get_optReason(self): return self.optReason + def set_optReason(self, optReason): self.optReason = optReason + def get_marketContext(self): return self.marketContext + def set_marketContext(self, marketContext): self.marketContext = marketContext + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_vavailability(self): return self.vavailability + def set_vavailability(self, vavailability): self.vavailability = vavailability + def get_createdDateTime(self): return self.createdDateTime + def set_createdDateTime(self, createdDateTime): self.createdDateTime = createdDateTime + def get_schemaVersion(self): return self.schemaVersion + def set_schemaVersion(self, schemaVersion): self.schemaVersion = schemaVersion + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + self.optID is not None or + self.optType is not None or + self.optReason is not None or + self.marketContext is not None or + self.venID is not None or + self.vavailability is not None or + self.createdDateTime is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='EiOptType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EiOptType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EiOptType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='EiOptType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='EiOptType'): + if self.schemaVersion is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + outfile.write(' schemaVersion=%s' % (quote_attrib(self.schemaVersion), )) + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + def exportChildren(self, outfile, level, namespace_='ei:', name_='EiOptType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.optID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optID), input_name='optID')), eol_)) + if self.optType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optType), input_name='optType')), eol_)) + if self.optReason is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optReason), input_name='optReason')), eol_)) + if self.marketContext is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.marketContext), input_name='marketContext')), eol_)) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + if self.vavailability is not None: + self.vavailability.export(outfile, level, namespace_='xcal:', name_='vavailability', pretty_print=pretty_print) + if self.createdDateTime is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.createdDateTime, input_name='createdDateTime'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('schemaVersion', node) + if value is not None and 'schemaVersion' not in already_processed: + already_processed.add('schemaVersion') + self.schemaVersion = value + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'optID': + optID_ = child_.text + optID_ = self.gds_validate_string(optID_, node, 'optID') + self.optID = optID_ + elif nodeName_ == 'optType': + optType_ = child_.text + if optType_: + optType_ = re_.sub(String_cleanup_pat_, " ", optType_).strip() + else: + optType_ = "" + optType_ = self.gds_validate_string(optType_, node, 'optType') + self.optType = optType_ + elif nodeName_ == 'optReason': + optReason_ = child_.text + optReason_ = self.gds_validate_string(optReason_, node, 'optReason') + self.optReason = optReason_ + elif nodeName_ == 'marketContext': + marketContext_ = child_.text + marketContext_ = self.gds_validate_string(marketContext_, node, 'marketContext') + self.marketContext = marketContext_ + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ + elif nodeName_ == 'vavailability': + obj_ = VavailabilityType.factory() + obj_.build(child_) + self.vavailability = obj_ + obj_.original_tagname_ = 'vavailability' + elif nodeName_ == 'createdDateTime': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.createdDateTime = dval_ +# end class EiOptType + + +class ReportPayloadType(StreamPayloadBaseType): + """Report Payload for use in Reports, snaps, and projections.""" + subclass = None + superclass = StreamPayloadBaseType + def __init__(self, rID=None, confidence=None, accuracy=None, payloadBase=None, extensiontype_=None): + self.original_tagname_ = None + super(ReportPayloadType, self).__init__(extensiontype_, ) + self.rID = rID + self.confidence = confidence + self.accuracy = accuracy + self.payloadBase = payloadBase + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReportPayloadType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReportPayloadType.subclass: + return ReportPayloadType.subclass(*args_, **kwargs_) + else: + return ReportPayloadType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_rID(self): return self.rID + def set_rID(self, rID): self.rID = rID + def get_confidence(self): return self.confidence + def set_confidence(self, confidence): self.confidence = confidence + def get_accuracy(self): return self.accuracy + def set_accuracy(self, accuracy): self.accuracy = accuracy + def get_payloadBase(self): return self.payloadBase + def set_payloadBase(self, payloadBase): self.payloadBase = payloadBase + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + self.rID is not None or + self.confidence is not None or + self.accuracy is not None or + self.payloadBase is not None or + super(ReportPayloadType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='ReportPayloadType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReportPayloadType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReportPayloadType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='ReportPayloadType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='ReportPayloadType'): + super(ReportPayloadType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ReportPayloadType') + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + def exportChildren(self, outfile, level, namespace_='ei:', name_='ReportPayloadType', fromsubclass_=False, pretty_print=True): + super(ReportPayloadType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.rID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.rID), input_name='rID')), eol_)) + if self.confidence is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.confidence, input_name='confidence'), eol_)) + if self.accuracy is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.accuracy, input_name='accuracy'), eol_)) + if self.payloadBase is not None: + self.payloadBase.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + super(ReportPayloadType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'rID': + rID_ = child_.text + rID_ = self.gds_validate_string(rID_, node, 'rID') + self.rID = rID_ + elif nodeName_ == 'confidence': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'confidence') + self.confidence = ival_ + elif nodeName_ == 'accuracy': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'accuracy') + self.accuracy = fval_ + elif nodeName_ == 'payloadBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.payloadBase = obj_ + obj_.original_tagname_ = 'payloadBase' + elif nodeName_ == 'oadrPayloadResourceStatus': + obj_ = oadrPayloadResourceStatusType.factory() + obj_.build(child_) + self.payloadBase = obj_ + obj_.original_tagname_ = 'oadrPayloadResourceStatus' + elif nodeName_ == 'payloadFloat': + obj_ = PayloadFloatType.factory() + obj_.build(child_) + self.payloadBase = obj_ + obj_.original_tagname_ = 'payloadFloat' + super(ReportPayloadType, self).buildChildren(child_, node, nodeName_, True) +# end class ReportPayloadType + + +class ReportSpecifierType(GeneratedsSuper): + """Parameters that define the content of a Report Stream""" + subclass = None + superclass = None + def __init__(self, reportSpecifierID=None, granularity=None, reportBackDuration=None, reportInterval=None, specifierPayload=None): + self.original_tagname_ = None + self.reportSpecifierID = reportSpecifierID + self.granularity = granularity + self.reportBackDuration = reportBackDuration + self.reportInterval = reportInterval + if specifierPayload is None: + self.specifierPayload = [] + else: + self.specifierPayload = specifierPayload + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReportSpecifierType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReportSpecifierType.subclass: + return ReportSpecifierType.subclass(*args_, **kwargs_) + else: + return ReportSpecifierType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_reportSpecifierID(self): return self.reportSpecifierID + def set_reportSpecifierID(self, reportSpecifierID): self.reportSpecifierID = reportSpecifierID + def get_granularity(self): return self.granularity + def set_granularity(self, granularity): self.granularity = granularity + def get_reportBackDuration(self): return self.reportBackDuration + def set_reportBackDuration(self, reportBackDuration): self.reportBackDuration = reportBackDuration + def get_reportInterval(self): return self.reportInterval + def set_reportInterval(self, reportInterval): self.reportInterval = reportInterval + def get_specifierPayload(self): return self.specifierPayload + def set_specifierPayload(self, specifierPayload): self.specifierPayload = specifierPayload + def add_specifierPayload(self, value): self.specifierPayload.append(value) + def insert_specifierPayload_at(self, index, value): self.specifierPayload.insert(index, value) + def replace_specifierPayload_at(self, index, value): self.specifierPayload[index] = value + def hasContent_(self): + if ( + self.reportSpecifierID is not None or + self.granularity is not None or + self.reportBackDuration is not None or + self.reportInterval is not None or + self.specifierPayload + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='ReportSpecifierType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReportSpecifierType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReportSpecifierType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='ReportSpecifierType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='ReportSpecifierType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='ReportSpecifierType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.reportSpecifierID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportSpecifierID), input_name='reportSpecifierID')), eol_)) + if self.granularity is not None: + self.granularity.export(outfile, level, namespace_='xcal:', name_='granularity', pretty_print=pretty_print) + if self.reportBackDuration is not None: + self.reportBackDuration.export(outfile, level, namespace_, name_='reportBackDuration', pretty_print=pretty_print) + if self.reportInterval is not None: + self.reportInterval.export(outfile, level, namespace_, name_='reportInterval', pretty_print=pretty_print) + for specifierPayload_ in self.specifierPayload: + specifierPayload_.export(outfile, level, namespace_='ei:', name_='specifierPayload', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'reportSpecifierID': + reportSpecifierID_ = child_.text + reportSpecifierID_ = self.gds_validate_string(reportSpecifierID_, node, 'reportSpecifierID') + self.reportSpecifierID = reportSpecifierID_ + elif nodeName_ == 'granularity': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.granularity = obj_ + obj_.original_tagname_ = 'granularity' + elif nodeName_ == 'reportBackDuration': + obj_ = DurationPropType.factory() + obj_.build(child_) + self.reportBackDuration = obj_ + obj_.original_tagname_ = 'reportBackDuration' + elif nodeName_ == 'reportInterval': + obj_ = WsCalendarIntervalType.factory() + obj_.build(child_) + self.reportInterval = obj_ + obj_.original_tagname_ = 'reportInterval' + elif nodeName_ == 'specifierPayload': + obj_ = SpecifierPayloadType.factory() + obj_.build(child_) + self.specifierPayload.append(obj_) + obj_.original_tagname_ = 'specifierPayload' +# end class ReportSpecifierType + + +class SpecifierPayloadType(GeneratedsSuper): + """Payload for use in Report Specifiers.""" + subclass = None + superclass = None + def __init__(self, rID=None, itemBase=None, readingType=None): + self.original_tagname_ = None + self.rID = rID + self.itemBase = itemBase + self.readingType = readingType + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SpecifierPayloadType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SpecifierPayloadType.subclass: + return SpecifierPayloadType.subclass(*args_, **kwargs_) + else: + return SpecifierPayloadType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_rID(self): return self.rID + def set_rID(self, rID): self.rID = rID + def get_itemBase(self): return self.itemBase + def set_itemBase(self, itemBase): self.itemBase = itemBase + def get_readingType(self): return self.readingType + def set_readingType(self, readingType): self.readingType = readingType + def hasContent_(self): + if ( + self.rID is not None or + self.itemBase is not None or + self.readingType is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='SpecifierPayloadType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SpecifierPayloadType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SpecifierPayloadType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='SpecifierPayloadType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='SpecifierPayloadType'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='SpecifierPayloadType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.rID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.rID), input_name='rID')), eol_)) + if self.itemBase is not None: + self.itemBase.export(outfile, level, namespace_, pretty_print=pretty_print) + if self.readingType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.readingType), input_name='readingType')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'rID': + rID_ = child_.text + rID_ = self.gds_validate_string(rID_, node, 'rID') + self.rID = rID_ + elif nodeName_ == 'itemBase': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'itemBase' + elif nodeName_ == 'customUnit': + obj_ = BaseUnitType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'customUnit' + elif nodeName_ == 'current': + obj_ = CurrentType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'current' + elif nodeName_ == 'currency': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currency' + elif nodeName_ == 'currencyPerKWh': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKWh' + elif nodeName_ == 'currencyPerKW': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerKW' + elif nodeName_ == 'currencyPerThm': + obj_ = currencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'currencyPerThm' + elif nodeName_ == 'frequency': + obj_ = FrequencyType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'frequency' + elif nodeName_ == 'Therm': + obj_ = ThermType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'Therm' + elif nodeName_ == 'temperature': + obj_ = temperatureType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'temperature' + elif nodeName_ == 'pulseCount': + obj_ = pulseCountType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'pulseCount' + elif nodeName_ == 'oadrGBDataDescription': + obj_ = oadrGBItemBase.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'oadrGBDataDescription' + elif nodeName_ == 'voltage': + obj_ = VoltageType.factory() + obj_.build(child_) + self.itemBase = obj_ + obj_.original_tagname_ = 'voltage' + elif nodeName_ == 'energyItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'energyItem' + elif nodeName_ == 'powerItem': + type_name_ = child_.attrib.get( + '{http://www.w3.org/2001/XMLSchema-instance}type') + if type_name_ is None: + type_name_ = child_.attrib.get('type') + if type_name_ is not None: + type_names_ = type_name_.split(':') + if len(type_names_) == 1: + type_name_ = type_names_[0] + else: + type_name_ = type_names_[1] + class_ = globals()[type_name_] + obj_ = class_.factory() + obj_.build(child_) + else: + raise NotImplementedError( + 'Class not implemented for element') + self.itemBase = obj_ + obj_.original_tagname_ = 'powerItem' + elif nodeName_ == 'energyApparent': + obj_ = EnergyApparentType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyApparent' + elif nodeName_ == 'energyReactive': + obj_ = EnergyReactiveType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReactive' + elif nodeName_ == 'energyReal': + obj_ = EnergyRealType.factory() + obj_.build(child_) + self.energyItem = obj_ + obj_.original_tagname_ = 'energyReal' + elif nodeName_ == 'powerApparent': + obj_ = PowerApparentType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerApparent' + elif nodeName_ == 'powerReactive': + obj_ = PowerReactiveType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReactive' + elif nodeName_ == 'powerReal': + obj_ = PowerRealType.factory() + obj_.build(child_) + self.powerItem = obj_ + obj_.original_tagname_ = 'powerReal' + elif nodeName_ == 'readingType': + readingType_ = child_.text + readingType_ = self.gds_validate_string(readingType_, node, 'readingType') + self.readingType = readingType_ +# end class SpecifierPayloadType + + +class registrationID(GeneratedsSuper): + """Identifier for Registration transaction. Not included in response to + query registration unless already registered""" + subclass = None + superclass = None + def __init__(self): + self.original_tagname_ = None + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, registrationID) + if subclass is not None: + return subclass(*args_, **kwargs_) + if registrationID.subclass: + return registrationID.subclass(*args_, **kwargs_) + else: + return registrationID(*args_, **kwargs_) + factory = staticmethod(factory) + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='registrationID', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('registrationID') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='registrationID') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='registrationID', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='registrationID'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='registrationID', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class registrationID + + +class refID(GeneratedsSuper): + """Reference ID for a particular instance, transmittal, or artifact. + Note: not the same as the native ID of the object being + transmitted or shared.""" + subclass = None + superclass = None + def __init__(self): + self.original_tagname_ = None + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, refID) + if subclass is not None: + return subclass(*args_, **kwargs_) + if refID.subclass: + return refID.subclass(*args_, **kwargs_) + else: + return refID(*args_, **kwargs_) + factory = staticmethod(factory) + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ei:', name_='refID', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('refID') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='refID') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ei:', name_='refID', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ei:', name_='refID'): + pass + def exportChildren(self, outfile, level, namespace_='ei:', name_='refID', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class refID + + +class EndDeviceAssetType(GeneratedsSuper): + """The EndDeviceAssets are the physical device or devices which could + be meters or other types of devices that may be of interest""" + subclass = None + superclass = None + def __init__(self, mrid=None): + self.original_tagname_ = None + self.mrid = mrid + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EndDeviceAssetType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EndDeviceAssetType.subclass: + return EndDeviceAssetType.subclass(*args_, **kwargs_) + else: + return EndDeviceAssetType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_mrid(self): return self.mrid + def set_mrid(self, mrid): self.mrid = mrid + def hasContent_(self): + if ( + self.mrid is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='EndDeviceAssetType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EndDeviceAssetType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EndDeviceAssetType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='EndDeviceAssetType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='EndDeviceAssetType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='EndDeviceAssetType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.mrid is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.mrid), input_name='mrid')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'mrid': + mrid_ = child_.text + mrid_ = self.gds_validate_string(mrid_, node, 'mrid') + self.mrid = mrid_ +# end class EndDeviceAssetType + + +class MeterAssetType(GeneratedsSuper): + """The MeterAsset is the physical device or devices that performs the + role of the meter""" + subclass = None + superclass = None + def __init__(self, mrid=None): + self.original_tagname_ = None + self.mrid = mrid + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, MeterAssetType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if MeterAssetType.subclass: + return MeterAssetType.subclass(*args_, **kwargs_) + else: + return MeterAssetType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_mrid(self): return self.mrid + def set_mrid(self, mrid): self.mrid = mrid + def hasContent_(self): + if ( + self.mrid is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='MeterAssetType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('MeterAssetType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='MeterAssetType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='MeterAssetType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='MeterAssetType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='MeterAssetType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.mrid is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.mrid), input_name='mrid')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'mrid': + mrid_ = child_.text + mrid_ = self.gds_validate_string(mrid_, node, 'mrid') + self.mrid = mrid_ +# end class MeterAssetType + + +class PnodeType(GeneratedsSuper): + """A pricing node is directly associated with a connectivity node. It + is a pricing location for which market participants submit their + bids, offers, buy/sell CRRs, and settle.""" + subclass = None + superclass = None + def __init__(self, node=None): + self.original_tagname_ = None + self.node = node + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PnodeType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PnodeType.subclass: + return PnodeType.subclass(*args_, **kwargs_) + else: + return PnodeType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_node(self): return self.node + def set_node(self, node): self.node = node + def hasContent_(self): + if ( + self.node is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PnodeType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PnodeType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PnodeType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PnodeType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PnodeType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='PnodeType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.node is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.node), input_name='node')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'node': + node_ = child_.text + node_ = self.gds_validate_string(node_, node, 'node') + self.node = node_ +# end class PnodeType + + +class AggregatedPnodeType(GeneratedsSuper): + """An aggregated pricing node is a specialized type of pricing node + used to model items such as System Zone, Default Price Zone, + Custom Price Zone, Control Area, Aggregated Generation, + Aggregated Participating Load, Aggregated Non-Participating + Load, Trading Hub, DCA Zone""" + subclass = None + superclass = None + def __init__(self, node=None): + self.original_tagname_ = None + self.node = node + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, AggregatedPnodeType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if AggregatedPnodeType.subclass: + return AggregatedPnodeType.subclass(*args_, **kwargs_) + else: + return AggregatedPnodeType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_node(self): return self.node + def set_node(self, node): self.node = node + def hasContent_(self): + if ( + self.node is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='AggregatedPnodeType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('AggregatedPnodeType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='AggregatedPnodeType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='AggregatedPnodeType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='AggregatedPnodeType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='AggregatedPnodeType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.node is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.node), input_name='node')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'node': + node_ = child_.text + node_ = self.gds_validate_string(node_, node, 'node') + self.node = node_ +# end class AggregatedPnodeType + + +class ServiceLocationType(GeneratedsSuper): + """A customer ServiceLocation has one or more ServiceDeliveryPoint(s), + which in turn relate to Meters. The location may be a point or a + polygon, depending on the specific circumstances. For + distribution, the ServiceLocation is typically the location of + the utility customer's premise.""" + subclass = None + superclass = None + def __init__(self, FeatureCollection=None): + self.original_tagname_ = None + self.FeatureCollection = FeatureCollection + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceLocationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceLocationType.subclass: + return ServiceLocationType.subclass(*args_, **kwargs_) + else: + return ServiceLocationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_FeatureCollection(self): return self.FeatureCollection + def set_FeatureCollection(self, FeatureCollection): self.FeatureCollection = FeatureCollection + def hasContent_(self): + if ( + self.FeatureCollection is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='ServiceLocationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceLocationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceLocationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='ServiceLocationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='ServiceLocationType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='ServiceLocationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.FeatureCollection is not None: + self.FeatureCollection.export(outfile, level, namespace_='gml:', name_='FeatureCollection', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'FeatureCollection': + obj_ = FeatureCollection.factory() + obj_.build(child_) + self.FeatureCollection = obj_ + obj_.original_tagname_ = 'FeatureCollection' +# end class ServiceLocationType + + +class ServiceDeliveryPointType(GeneratedsSuper): + """Logical point on the network where the ownership of the service + changes hands. It is one of potentially many service points + within a ServiceLocation, delivering service in accordance with + a CustomerAgreement. Used at the place where a meter may be + installed.""" + subclass = None + superclass = None + def __init__(self, node=None): + self.original_tagname_ = None + self.node = node + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceDeliveryPointType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceDeliveryPointType.subclass: + return ServiceDeliveryPointType.subclass(*args_, **kwargs_) + else: + return ServiceDeliveryPointType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_node(self): return self.node + def set_node(self, node): self.node = node + def hasContent_(self): + if ( + self.node is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='ServiceDeliveryPointType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceDeliveryPointType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceDeliveryPointType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='ServiceDeliveryPointType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='ServiceDeliveryPointType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='ServiceDeliveryPointType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.node is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.node), input_name='node')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'node': + node_ = child_.text + node_ = self.gds_validate_string(node_, node, 'node') + self.node = node_ +# end class ServiceDeliveryPointType + + +class TransportInterfaceType(GeneratedsSuper): + """The Transport Interface delineates the edges at either end of a + transport segment.""" + subclass = None + superclass = None + def __init__(self, pointOfReceipt=None, pointOfDelivery=None): + self.original_tagname_ = None + self.pointOfReceipt = pointOfReceipt + self.validate_NodeType(self.pointOfReceipt) + self.pointOfDelivery = pointOfDelivery + self.validate_NodeType(self.pointOfDelivery) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, TransportInterfaceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if TransportInterfaceType.subclass: + return TransportInterfaceType.subclass(*args_, **kwargs_) + else: + return TransportInterfaceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_pointOfReceipt(self): return self.pointOfReceipt + def set_pointOfReceipt(self, pointOfReceipt): self.pointOfReceipt = pointOfReceipt + def get_pointOfDelivery(self): return self.pointOfDelivery + def set_pointOfDelivery(self, pointOfDelivery): self.pointOfDelivery = pointOfDelivery + def validate_NodeType(self, value): + # Validate type NodeType, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.pointOfReceipt is not None or + self.pointOfDelivery is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='TransportInterfaceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('TransportInterfaceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='TransportInterfaceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='TransportInterfaceType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='TransportInterfaceType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='TransportInterfaceType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.pointOfReceipt is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.pointOfReceipt), input_name='pointOfReceipt')), eol_)) + if self.pointOfDelivery is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.pointOfDelivery), input_name='pointOfDelivery')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'pointOfReceipt': + pointOfReceipt_ = child_.text + pointOfReceipt_ = self.gds_validate_string(pointOfReceipt_, node, 'pointOfReceipt') + self.pointOfReceipt = pointOfReceipt_ + # validate type NodeType + self.validate_NodeType(self.pointOfReceipt) + elif nodeName_ == 'pointOfDelivery': + pointOfDelivery_ = child_.text + pointOfDelivery_ = self.gds_validate_string(pointOfDelivery_, node, 'pointOfDelivery') + self.pointOfDelivery = pointOfDelivery_ + # validate type NodeType + self.validate_NodeType(self.pointOfDelivery) +# end class TransportInterfaceType + + +class EnergyApparentType(GeneratedsSuper): + """Apparent Energy, measured in volt-ampere hours (VAh)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EnergyApparentType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EnergyApparentType.subclass: + return EnergyApparentType.subclass(*args_, **kwargs_) + else: + return EnergyApparentType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='EnergyApparentType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EnergyApparentType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EnergyApparentType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='EnergyApparentType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='EnergyApparentType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='EnergyApparentType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ +# end class EnergyApparentType + + +class EnergyReactiveType(GeneratedsSuper): + """Reactive Energy, volt-amperes reactive hours (VARh)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EnergyReactiveType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EnergyReactiveType.subclass: + return EnergyReactiveType.subclass(*args_, **kwargs_) + else: + return EnergyReactiveType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='EnergyReactiveType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EnergyReactiveType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EnergyReactiveType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='EnergyReactiveType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='EnergyReactiveType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='EnergyReactiveType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ +# end class EnergyReactiveType + + +class EnergyRealType(GeneratedsSuper): + """Real Energy, Watt Hours (Wh)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EnergyRealType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EnergyRealType.subclass: + return EnergyRealType.subclass(*args_, **kwargs_) + else: + return EnergyRealType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='EnergyRealType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EnergyRealType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EnergyRealType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='EnergyRealType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='EnergyRealType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='EnergyRealType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ +# end class EnergyRealType + + +class PowerApparentType(GeneratedsSuper): + """Apparent Power measured in volt-amperes (VA)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None, powerAttributes=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + self.powerAttributes = powerAttributes + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PowerApparentType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PowerApparentType.subclass: + return PowerApparentType.subclass(*args_, **kwargs_) + else: + return PowerApparentType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def get_powerAttributes(self): return self.powerAttributes + def set_powerAttributes(self, powerAttributes): self.powerAttributes = powerAttributes + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + self.powerAttributes is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PowerApparentType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PowerApparentType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PowerApparentType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PowerApparentType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PowerApparentType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='PowerApparentType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + if self.powerAttributes is not None: + self.powerAttributes.export(outfile, level, namespace_='power:', name_='powerAttributes', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + elif nodeName_ == 'powerAttributes': + obj_ = PowerAttributesType.factory() + obj_.build(child_) + self.powerAttributes = obj_ + obj_.original_tagname_ = 'powerAttributes' +# end class PowerApparentType + + +class PowerReactiveType(GeneratedsSuper): + """Reactive power, measured in volt-amperes reactive (VAR)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None, powerAttributes=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + self.powerAttributes = powerAttributes + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PowerReactiveType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PowerReactiveType.subclass: + return PowerReactiveType.subclass(*args_, **kwargs_) + else: + return PowerReactiveType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def get_powerAttributes(self): return self.powerAttributes + def set_powerAttributes(self, powerAttributes): self.powerAttributes = powerAttributes + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + self.powerAttributes is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PowerReactiveType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PowerReactiveType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PowerReactiveType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PowerReactiveType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PowerReactiveType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='PowerReactiveType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + if self.powerAttributes is not None: + self.powerAttributes.export(outfile, level, namespace_='power:', name_='powerAttributes', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + elif nodeName_ == 'powerAttributes': + obj_ = PowerAttributesType.factory() + obj_.build(child_) + self.powerAttributes = obj_ + obj_.original_tagname_ = 'powerAttributes' +# end class PowerReactiveType + + +class PowerRealType(GeneratedsSuper): + """Real power measured in Watts (W) or Joules/second (J/s)""" + subclass = None + superclass = None + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None, powerAttributes=None): + self.original_tagname_ = None + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.validate_itemUnitsType(self.itemUnits) + self.siScaleCode = siScaleCode + self.powerAttributes = powerAttributes + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PowerRealType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PowerRealType.subclass: + return PowerRealType.subclass(*args_, **kwargs_) + else: + return PowerRealType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def get_powerAttributes(self): return self.powerAttributes + def set_powerAttributes(self, powerAttributes): self.powerAttributes = powerAttributes + def validate_itemUnitsType(self, value): + # Validate type itemUnitsType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['W', 'J/s'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on itemUnitsType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + self.powerAttributes is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PowerRealType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:scale="http://docs.oasis-open.org/ns/emix/2011/06/siscale" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PowerRealType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PowerRealType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PowerRealType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PowerRealType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='PowerRealType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + if self.powerAttributes is not None: + self.powerAttributes.export(outfile, level, namespace_='power:', name_='powerAttributes', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + if itemUnits_: + itemUnits_ = re_.sub(String_cleanup_pat_, " ", itemUnits_).strip() + else: + itemUnits_ = "" + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + # validate type itemUnitsType + self.validate_itemUnitsType(self.itemUnits) + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + elif nodeName_ == 'powerAttributes': + obj_ = PowerAttributesType.factory() + obj_.build(child_) + self.powerAttributes = obj_ + obj_.original_tagname_ = 'powerAttributes' +# end class PowerRealType + + +class PowerAttributesType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, hertz=None, voltage=None, ac=None): + self.original_tagname_ = None + self.hertz = hertz + self.voltage = voltage + self.ac = ac + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PowerAttributesType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PowerAttributesType.subclass: + return PowerAttributesType.subclass(*args_, **kwargs_) + else: + return PowerAttributesType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_hertz(self): return self.hertz + def set_hertz(self, hertz): self.hertz = hertz + def get_voltage(self): return self.voltage + def set_voltage(self, voltage): self.voltage = voltage + def get_ac(self): return self.ac + def set_ac(self, ac): self.ac = ac + def hasContent_(self): + if ( + self.hertz is not None or + self.voltage is not None or + self.ac is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PowerAttributesType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PowerAttributesType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PowerAttributesType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PowerAttributesType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PowerAttributesType'): + pass + def exportChildren(self, outfile, level, namespace_='power:', name_='PowerAttributesType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.hertz is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.hertz, input_name='hertz'), eol_)) + if self.voltage is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.voltage, input_name='voltage'), eol_)) + if self.ac is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.ac, input_name='ac'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'hertz': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'hertz') + self.hertz = fval_ + elif nodeName_ == 'voltage': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'voltage') + self.voltage = fval_ + elif nodeName_ == 'ac': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'ac') + self.ac = ival_ +# end class PowerAttributesType + + +class FeatureCollection(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, id=None, location=None): + self.original_tagname_ = None + self.id = _cast(None, id) + self.location = location + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, FeatureCollection) + if subclass is not None: + return subclass(*args_, **kwargs_) + if FeatureCollection.subclass: + return FeatureCollection.subclass(*args_, **kwargs_) + else: + return FeatureCollection(*args_, **kwargs_) + factory = staticmethod(factory) + def get_location(self): return self.location + def set_location(self, location): self.location = location + def get_id(self): return self.id + def set_id(self, id): self.id = id + def hasContent_(self): + if ( + self.location is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='gml:', name_='FeatureCollection', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:gml="http://www.opengis.net/gml/3.2"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('FeatureCollection') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='FeatureCollection') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='gml:', name_='FeatureCollection', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='gml:', name_='FeatureCollection'): + if self.id is not None and 'id' not in already_processed: + already_processed.add('id') + outfile.write(' id=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.id), input_name='id')), )) + def exportChildren(self, outfile, level, namespace_='gml:', name_='FeatureCollection', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.location is not None: + self.location.export(outfile, level, namespace_, name_='location', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('id', node) + if value is not None and 'id' not in already_processed: + already_processed.add('id') + self.id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'location': + obj_ = locationType.factory() + obj_.build(child_) + self.location = obj_ + obj_.original_tagname_ = 'location' +# end class FeatureCollection + + +class ServiceAreaType(GeneratedsSuper): + """The Service Area is the geographic region that is affected by the + EMIX market condition""" + subclass = None + superclass = None + def __init__(self, FeatureCollection=None): + self.original_tagname_ = None + self.FeatureCollection = FeatureCollection + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceAreaType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceAreaType.subclass: + return ServiceAreaType.subclass(*args_, **kwargs_) + else: + return ServiceAreaType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_FeatureCollection(self): return self.FeatureCollection + def set_FeatureCollection(self, FeatureCollection): self.FeatureCollection = FeatureCollection + def hasContent_(self): + if ( + self.FeatureCollection is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='emix:', name_='ServiceAreaType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceAreaType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceAreaType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='emix:', name_='ServiceAreaType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='emix:', name_='ServiceAreaType'): + pass + def exportChildren(self, outfile, level, namespace_='emix:', name_='ServiceAreaType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.FeatureCollection is not None: + self.FeatureCollection.export(outfile, level, namespace_='gml:', name_='FeatureCollection', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'FeatureCollection': + obj_ = FeatureCollection.factory() + obj_.build(child_) + self.FeatureCollection = obj_ + obj_.original_tagname_ = 'FeatureCollection' +# end class ServiceAreaType + + +class ItemBaseType(GeneratedsSuper): + """Abstract base type for units for EMIX Product delivery, measurement, + and warrants.""" + subclass = None + superclass = None + def __init__(self, extensiontype_=None): + self.original_tagname_ = None + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ItemBaseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ItemBaseType.subclass: + return ItemBaseType.subclass(*args_, **kwargs_) + else: + return ItemBaseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='emix:', name_='ItemBaseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ItemBaseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ItemBaseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='emix:', name_='ItemBaseType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='emix:', name_='ItemBaseType'): + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + pass + def exportChildren(self, outfile, level, namespace_='emix:', name_='ItemBaseType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class ItemBaseType + + +class eiRequestEvent(GeneratedsSuper): + """Request Event from a VTN in pull mode""" + subclass = None + superclass = None + def __init__(self, requestID=None, venID=None, replyLimit=None): + self.original_tagname_ = None + self.requestID = requestID + self.venID = venID + self.replyLimit = replyLimit + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiRequestEvent) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiRequestEvent.subclass: + return eiRequestEvent.subclass(*args_, **kwargs_) + else: + return eiRequestEvent(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def get_replyLimit(self): return self.replyLimit + def set_replyLimit(self, replyLimit): self.replyLimit = replyLimit + def hasContent_(self): + if ( + self.requestID is not None or + self.venID is not None or + self.replyLimit is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='pyld:', name_='eiRequestEvent', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiRequestEvent') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiRequestEvent') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='pyld:', name_='eiRequestEvent', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='pyld:', name_='eiRequestEvent'): + pass + def exportChildren(self, outfile, level, namespace_='pyld:', name_='eiRequestEvent', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + if self.replyLimit is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.replyLimit, input_name='replyLimit'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ + elif nodeName_ == 'replyLimit': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'replyLimit') + self.replyLimit = ival_ +# end class eiRequestEvent + + +class eiCreatedEvent(GeneratedsSuper): + """Respond to a DR Event with optIn or optOut""" + subclass = None + superclass = None + def __init__(self, eiResponse=None, eventResponses=None, venID=None): + self.original_tagname_ = None + self.eiResponse = eiResponse + self.eventResponses = eventResponses + self.venID = venID + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiCreatedEvent) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiCreatedEvent.subclass: + return eiCreatedEvent.subclass(*args_, **kwargs_) + else: + return eiCreatedEvent(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiResponse(self): return self.eiResponse + def set_eiResponse(self, eiResponse): self.eiResponse = eiResponse + def get_eventResponses(self): return self.eventResponses + def set_eventResponses(self, eventResponses): self.eventResponses = eventResponses + def get_venID(self): return self.venID + def set_venID(self, venID): self.venID = venID + def hasContent_(self): + if ( + self.eiResponse is not None or + self.eventResponses is not None or + self.venID is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='pyld:', name_='eiCreatedEvent', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiCreatedEvent') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiCreatedEvent') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='pyld:', name_='eiCreatedEvent', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='pyld:', name_='eiCreatedEvent'): + pass + def exportChildren(self, outfile, level, namespace_='pyld:', name_='eiCreatedEvent', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiResponse is not None: + self.eiResponse.export(outfile, level, namespace_='ei:', name_='eiResponse', pretty_print=pretty_print) + if self.eventResponses is not None: + self.eventResponses.export(outfile, level, namespace_='ei:', name_='eventResponses', pretty_print=pretty_print) + if self.venID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.venID), input_name='venID')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiResponse': + obj_ = EiResponseType.factory() + obj_.build(child_) + self.eiResponse = obj_ + obj_.original_tagname_ = 'eiResponse' + elif nodeName_ == 'eventResponses': + obj_ = eventResponses.factory() + obj_.build(child_) + self.eventResponses = obj_ + obj_.original_tagname_ = 'eventResponses' + elif nodeName_ == 'venID': + venID_ = child_.text + venID_ = self.gds_validate_string(venID_, node, 'venID') + self.venID = venID_ +# end class eiCreatedEvent + + +class Object(GeneratedsSuper): + """Superclass of all object classes to allow extensions.""" + subclass = None + superclass = None + def __init__(self, extension=None): + self.original_tagname_ = None + if extension is None: + self.extension = [] + else: + self.extension = extension + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, Object) + if subclass is not None: + return subclass(*args_, **kwargs_) + if Object.subclass: + return Object.subclass(*args_, **kwargs_) + else: + return Object(*args_, **kwargs_) + factory = staticmethod(factory) + def get_extension(self): return self.extension + def set_extension(self, extension): self.extension = extension + def add_extension(self, value): self.extension.append(value) + def insert_extension_at(self, index, value): self.extension.insert(index, value) + def replace_extension_at(self, index, value): self.extension[index] = value + def hasContent_(self): + if ( + self.extension + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='Object', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('Object') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='Object') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='Object', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='Object'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='Object', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for extension_ in self.extension: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(extension_), input_name='extension')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'extension': + extension_ = child_.text + extension_ = self.gds_validate_string(extension_, node, 'extension') + self.extension.append(extension_) +# end class Object + + +class ServiceStatus(Object): + """Contains the current status of the service.""" + subclass = None + superclass = Object + def __init__(self, currentStatus=None): + self.original_tagname_ = None + super(ServiceStatus, self).__init__() + self.currentStatus = currentStatus + self.validate_ESPIServiceStatus(self.currentStatus) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceStatus) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceStatus.subclass: + return ServiceStatus.subclass(*args_, **kwargs_) + else: + return ServiceStatus(*args_, **kwargs_) + factory = staticmethod(factory) + def get_currentStatus(self): return self.currentStatus + def set_currentStatus(self, currentStatus): self.currentStatus = currentStatus + def validate_ESPIServiceStatus(self, value): + # Validate type ESPIServiceStatus, a restriction on UInt16. + pass + def hasContent_(self): + if ( + self.currentStatus is not None or + super(ServiceStatus, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ServiceStatus', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceStatus') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceStatus') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ServiceStatus', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ServiceStatus'): + super(ServiceStatus, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceStatus') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ServiceStatus', fromsubclass_=False, pretty_print=True): + super(ServiceStatus, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.currentStatus is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.currentStatus, input_name='currentStatus'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ServiceStatus, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'currentStatus': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'currentStatus') + self.currentStatus = ival_ + # validate type ESPIServiceStatus + self.validate_ESPIServiceStatus(self.currentStatus) + super(ServiceStatus, self).buildChildren(child_, node, nodeName_, True) +# end class ServiceStatus + + +class RationalNumber(GeneratedsSuper): + """[extension] Rational number = 'numerator' / 'denominator'.""" + subclass = None + superclass = None + def __init__(self, numerator=None, denominator=None): + self.original_tagname_ = None + self.numerator = numerator + self.denominator = denominator + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, RationalNumber) + if subclass is not None: + return subclass(*args_, **kwargs_) + if RationalNumber.subclass: + return RationalNumber.subclass(*args_, **kwargs_) + else: + return RationalNumber(*args_, **kwargs_) + factory = staticmethod(factory) + def get_numerator(self): return self.numerator + def set_numerator(self, numerator): self.numerator = numerator + def get_denominator(self): return self.denominator + def set_denominator(self, denominator): self.denominator = denominator + def hasContent_(self): + if ( + self.numerator is not None or + self.denominator is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='RationalNumber', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('RationalNumber') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='RationalNumber') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='RationalNumber', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='RationalNumber'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='RationalNumber', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.numerator is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.numerator, input_name='numerator'), eol_)) + if self.denominator is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.denominator), input_name='denominator')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'numerator': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'numerator') + self.numerator = ival_ + elif nodeName_ == 'denominator': + denominator_ = child_.text + denominator_ = self.gds_validate_string(denominator_, node, 'denominator') + self.denominator = denominator_ +# end class RationalNumber + + +class denominator(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self): + self.original_tagname_ = None + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, denominator) + if subclass is not None: + return subclass(*args_, **kwargs_) + if denominator.subclass: + return denominator.subclass(*args_, **kwargs_) + else: + return denominator(*args_, **kwargs_) + factory = staticmethod(factory) + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='denominator', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('denominator') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='denominator') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='denominator', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='denominator'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='denominator', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class denominator + + +class ReadingInterharmonic(GeneratedsSuper): + """[extension] Interharmonics are represented as a rational number + 'numerator' / 'denominator', and harmonics are represented using + the same mechanism and identified by 'denominator'=1.""" + subclass = None + superclass = None + def __init__(self, numerator=None, denominator=None): + self.original_tagname_ = None + self.numerator = numerator + self.denominator = denominator + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReadingInterharmonic) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReadingInterharmonic.subclass: + return ReadingInterharmonic.subclass(*args_, **kwargs_) + else: + return ReadingInterharmonic(*args_, **kwargs_) + factory = staticmethod(factory) + def get_numerator(self): return self.numerator + def set_numerator(self, numerator): self.numerator = numerator + def get_denominator(self): return self.denominator + def set_denominator(self, denominator): self.denominator = denominator + def hasContent_(self): + if ( + self.numerator is not None or + self.denominator is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ReadingInterharmonic', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReadingInterharmonic') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReadingInterharmonic') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ReadingInterharmonic', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ReadingInterharmonic'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ReadingInterharmonic', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.numerator is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.numerator, input_name='numerator'), eol_)) + if self.denominator is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.denominator), input_name='denominator')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'numerator': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'numerator') + self.numerator = ival_ + elif nodeName_ == 'denominator': + denominator_ = child_.text + denominator_ = self.gds_validate_string(denominator_, node, 'denominator') + self.denominator = denominator_ +# end class ReadingInterharmonic + + +class LineItem(GeneratedsSuper): + """[extension] Line item of detail for additional cost""" + subclass = None + superclass = None + def __init__(self, amount=None, rounding=None, dateTime=None, note=None): + self.original_tagname_ = None + self.amount = amount + self.validate_Int48(self.amount) + self.rounding = rounding + self.validate_Int48(self.rounding) + self.dateTime = dateTime + self.validate_TimeType(self.dateTime) + self.note = note + self.validate_String256(self.note) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, LineItem) + if subclass is not None: + return subclass(*args_, **kwargs_) + if LineItem.subclass: + return LineItem.subclass(*args_, **kwargs_) + else: + return LineItem(*args_, **kwargs_) + factory = staticmethod(factory) + def get_amount(self): return self.amount + def set_amount(self, amount): self.amount = amount + def get_rounding(self): return self.rounding + def set_rounding(self, rounding): self.rounding = rounding + def get_dateTime(self): return self.dateTime + def set_dateTime(self, dateTime): self.dateTime = dateTime + def get_note(self): return self.note + def set_note(self, note): self.note = note + def validate_Int48(self, value): + # Validate type Int48, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + if value < -140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd minInclusive restriction on Int48' % {"value" : value} ) + if value > 140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd maxInclusive restriction on Int48' % {"value" : value} ) + def validate_TimeType(self, value): + # Validate type TimeType, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + pass + def validate_String256(self, value): + # Validate type String256, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if len(value) > 256: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on String256' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.amount is not None or + self.rounding is not None or + self.dateTime is not None or + self.note is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='LineItem', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('LineItem') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='LineItem') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='LineItem', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='LineItem'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='LineItem', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.amount is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.amount, input_name='amount'), eol_)) + if self.rounding is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.rounding, input_name='rounding'), eol_)) + if self.dateTime is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.dateTime, input_name='dateTime'), eol_)) + if self.note is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.note), input_name='note')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'amount': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'amount') + self.amount = ival_ + # validate type Int48 + self.validate_Int48(self.amount) + elif nodeName_ == 'rounding': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'rounding') + self.rounding = ival_ + # validate type Int48 + self.validate_Int48(self.rounding) + elif nodeName_ == 'dateTime': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'dateTime') + self.dateTime = ival_ + # validate type TimeType + self.validate_TimeType(self.dateTime) + elif nodeName_ == 'note': + note_ = child_.text + note_ = self.gds_validate_string(note_, node, 'note') + self.note = note_ + # validate type String256 + self.validate_String256(self.note) +# end class LineItem + + +class textType(GeneratedsSuper): + """The Atom text construct is defined in section 3.1 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, type_=None, base=None, lang=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.type_ = _cast(None, type_) + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, textType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if textType.subclass: + return textType.subclass(*args_, **kwargs_) + else: + return textType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_type(self): return self.type_ + def set_type(self, type_): self.type_ = type_ + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ is not None or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='textType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('textType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='textType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='textType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='textType'): + if self.type_ is not None and 'type_' not in already_processed: + already_processed.add('type_') + outfile.write(' type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.type_), input_name='type')), )) + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='textType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('type', node) + if value is not None and 'type' not in already_processed: + already_processed.add('type') + self.type_ = value + self.type_ = ' '.join(self.type_.split()) + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class textType + + +class personType(GeneratedsSuper): + """The Atom person construct is defined in section 3.2 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, name=None, uri=None, email=None, anytypeobjs_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + if name is None: + self.name = [] + else: + self.name = name + if uri is None: + self.uri = [] + else: + self.uri = uri + if email is None: + self.email = [] + else: + self.email = email + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, personType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if personType.subclass: + return personType.subclass(*args_, **kwargs_) + else: + return personType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_name(self): return self.name + def set_name(self, name): self.name = name + def add_name(self, value): self.name.append(value) + def insert_name_at(self, index, value): self.name.insert(index, value) + def replace_name_at(self, index, value): self.name[index] = value + def get_uri(self): return self.uri + def set_uri(self, uri): self.uri = uri + def add_uri(self, value): self.uri.append(value) + def insert_uri_at(self, index, value): self.uri.insert(index, value) + def replace_uri_at(self, index, value): self.uri[index] = value + def get_email(self): return self.email + def set_email(self, email): self.email = email + def add_email(self, value): self.email.append(value) + def insert_email_at(self, index, value): self.email.insert(index, value) + def replace_email_at(self, index, value): self.email[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def validate_emailType(self, value): + # Validate type emailType, a restriction on xs:normalizedString. + if value is not None and Validate_simpletypes_: + if not self.gds_validate_simple_patterns( + self.validate_emailType_patterns_, value): + warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_emailType_patterns_, )) + validate_emailType_patterns_ = [['^\\w+@(\\w+\\.)+\\w+$']] + def hasContent_(self): + if ( + self.name or + self.uri or + self.email or + self.anytypeobjs_ is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='personType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('personType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='personType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='personType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='personType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='personType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for name_ in self.name: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(name_), input_name='name')), eol_)) + for uri_ in self.uri: + uri_.export(outfile, level, namespace_, name_='uri', pretty_print=pretty_print) + for email_ in self.email: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(email_), input_name='email')), eol_)) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'name': + name_ = child_.text + name_ = self.gds_validate_string(name_, node, 'name') + self.name.append(name_) + elif nodeName_ == 'uri': + obj_ = uriType.factory() + obj_.build(child_) + self.uri.append(obj_) + obj_.original_tagname_ = 'uri' + elif nodeName_ == 'email': + email_ = child_.text + email_ = self.gds_validate_string(email_, node, 'email') + self.email.append(email_) + # validate type emailType + self.validate_emailType(self.email[-1]) + else: + obj_ = self.gds_build_any(child_, 'personType') + if obj_ is not None: + self.set_anytypeobjs_(obj_) +# end class personType + + +class feedType(GeneratedsSuper): + """The Atom feed construct is defined in section 4.1.1 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, author=None, category=None, contributor=None, generator=None, icon=None, id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, entry=None, anytypeobjs_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + if author is None: + self.author = [] + else: + self.author = author + if category is None: + self.category = [] + else: + self.category = category + if contributor is None: + self.contributor = [] + else: + self.contributor = contributor + if generator is None: + self.generator = [] + else: + self.generator = generator + if icon is None: + self.icon = [] + else: + self.icon = icon + if id is None: + self.id = [] + else: + self.id = id + if link is None: + self.link = [] + else: + self.link = link + if logo is None: + self.logo = [] + else: + self.logo = logo + if rights is None: + self.rights = [] + else: + self.rights = rights + if subtitle is None: + self.subtitle = [] + else: + self.subtitle = subtitle + if title is None: + self.title = [] + else: + self.title = title + if updated is None: + self.updated = [] + else: + self.updated = updated + if entry is None: + self.entry = [] + else: + self.entry = entry + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, feedType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if feedType.subclass: + return feedType.subclass(*args_, **kwargs_) + else: + return feedType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_author(self): return self.author + def set_author(self, author): self.author = author + def add_author(self, value): self.author.append(value) + def insert_author_at(self, index, value): self.author.insert(index, value) + def replace_author_at(self, index, value): self.author[index] = value + def get_category(self): return self.category + def set_category(self, category): self.category = category + def add_category(self, value): self.category.append(value) + def insert_category_at(self, index, value): self.category.insert(index, value) + def replace_category_at(self, index, value): self.category[index] = value + def get_contributor(self): return self.contributor + def set_contributor(self, contributor): self.contributor = contributor + def add_contributor(self, value): self.contributor.append(value) + def insert_contributor_at(self, index, value): self.contributor.insert(index, value) + def replace_contributor_at(self, index, value): self.contributor[index] = value + def get_generator(self): return self.generator + def set_generator(self, generator): self.generator = generator + def add_generator(self, value): self.generator.append(value) + def insert_generator_at(self, index, value): self.generator.insert(index, value) + def replace_generator_at(self, index, value): self.generator[index] = value + def get_icon(self): return self.icon + def set_icon(self, icon): self.icon = icon + def add_icon(self, value): self.icon.append(value) + def insert_icon_at(self, index, value): self.icon.insert(index, value) + def replace_icon_at(self, index, value): self.icon[index] = value + def get_id(self): return self.id + def set_id(self, id): self.id = id + def add_id(self, value): self.id.append(value) + def insert_id_at(self, index, value): self.id.insert(index, value) + def replace_id_at(self, index, value): self.id[index] = value + def get_link(self): return self.link + def set_link(self, link): self.link = link + def add_link(self, value): self.link.append(value) + def insert_link_at(self, index, value): self.link.insert(index, value) + def replace_link_at(self, index, value): self.link[index] = value + def get_logo(self): return self.logo + def set_logo(self, logo): self.logo = logo + def add_logo(self, value): self.logo.append(value) + def insert_logo_at(self, index, value): self.logo.insert(index, value) + def replace_logo_at(self, index, value): self.logo[index] = value + def get_rights(self): return self.rights + def set_rights(self, rights): self.rights = rights + def add_rights(self, value): self.rights.append(value) + def insert_rights_at(self, index, value): self.rights.insert(index, value) + def replace_rights_at(self, index, value): self.rights[index] = value + def get_subtitle(self): return self.subtitle + def set_subtitle(self, subtitle): self.subtitle = subtitle + def add_subtitle(self, value): self.subtitle.append(value) + def insert_subtitle_at(self, index, value): self.subtitle.insert(index, value) + def replace_subtitle_at(self, index, value): self.subtitle[index] = value + def get_title(self): return self.title + def set_title(self, title): self.title = title + def add_title(self, value): self.title.append(value) + def insert_title_at(self, index, value): self.title.insert(index, value) + def replace_title_at(self, index, value): self.title[index] = value + def get_updated(self): return self.updated + def set_updated(self, updated): self.updated = updated + def add_updated(self, value): self.updated.append(value) + def insert_updated_at(self, index, value): self.updated.insert(index, value) + def replace_updated_at(self, index, value): self.updated[index] = value + def get_entry(self): return self.entry + def set_entry(self, entry): self.entry = entry + def add_entry(self, value): self.entry.append(value) + def insert_entry_at(self, index, value): self.entry.insert(index, value) + def replace_entry_at(self, index, value): self.entry[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def hasContent_(self): + if ( + self.author or + self.category or + self.contributor or + self.generator or + self.icon or + self.id or + self.link or + self.logo or + self.rights or + self.subtitle or + self.title or + self.updated or + self.entry or + self.anytypeobjs_ + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='feedType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('feedType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='feedType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='feedType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='feedType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='feedType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for author_ in self.author: + author_.export(outfile, level, namespace_, name_='author', pretty_print=pretty_print) + for category_ in self.category: + category_.export(outfile, level, namespace_, name_='category', pretty_print=pretty_print) + for contributor_ in self.contributor: + contributor_.export(outfile, level, namespace_, name_='contributor', pretty_print=pretty_print) + for generator_ in self.generator: + generator_.export(outfile, level, namespace_, name_='generator', pretty_print=pretty_print) + for icon_ in self.icon: + icon_.export(outfile, level, namespace_, name_='icon', pretty_print=pretty_print) + for id_ in self.id: + id_.export(outfile, level, namespace_, name_='id', pretty_print=pretty_print) + for link_ in self.link: + link_.export(outfile, level, namespace_, name_='link', pretty_print=pretty_print) + for logo_ in self.logo: + logo_.export(outfile, level, namespace_, name_='logo', pretty_print=pretty_print) + for rights_ in self.rights: + rights_.export(outfile, level, namespace_, name_='rights', pretty_print=pretty_print) + for subtitle_ in self.subtitle: + subtitle_.export(outfile, level, namespace_, name_='subtitle', pretty_print=pretty_print) + for title_ in self.title: + title_.export(outfile, level, namespace_, name_='title', pretty_print=pretty_print) + for updated_ in self.updated: + updated_.export(outfile, level, namespace_, name_='updated', pretty_print=pretty_print) + for entry_ in self.entry: + entry_.export(outfile, level, namespace_, name_='entry', pretty_print=pretty_print) + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'author': + obj_ = personType.factory() + obj_.build(child_) + self.author.append(obj_) + obj_.original_tagname_ = 'author' + elif nodeName_ == 'category': + obj_ = categoryType.factory() + obj_.build(child_) + self.category.append(obj_) + obj_.original_tagname_ = 'category' + elif nodeName_ == 'contributor': + obj_ = personType.factory() + obj_.build(child_) + self.contributor.append(obj_) + obj_.original_tagname_ = 'contributor' + elif nodeName_ == 'generator': + obj_ = generatorType.factory() + obj_.build(child_) + self.generator.append(obj_) + obj_.original_tagname_ = 'generator' + elif nodeName_ == 'icon': + obj_ = iconType.factory() + obj_.build(child_) + self.icon.append(obj_) + obj_.original_tagname_ = 'icon' + elif nodeName_ == 'id': + obj_ = idType.factory() + obj_.build(child_) + self.id.append(obj_) + obj_.original_tagname_ = 'id' + elif nodeName_ == 'link': + obj_ = linkType.factory() + obj_.build(child_) + self.link.append(obj_) + obj_.original_tagname_ = 'link' + elif nodeName_ == 'logo': + obj_ = logoType.factory() + obj_.build(child_) + self.logo.append(obj_) + obj_.original_tagname_ = 'logo' + elif nodeName_ == 'rights': + obj_ = textType.factory() + obj_.build(child_) + self.rights.append(obj_) + obj_.original_tagname_ = 'rights' + elif nodeName_ == 'subtitle': + obj_ = textType.factory() + obj_.build(child_) + self.subtitle.append(obj_) + obj_.original_tagname_ = 'subtitle' + elif nodeName_ == 'title': + obj_ = textType.factory() + obj_.build(child_) + self.title.append(obj_) + obj_.original_tagname_ = 'title' + elif nodeName_ == 'updated': + obj_ = dateTimeType.factory() + obj_.build(child_) + self.updated.append(obj_) + obj_.original_tagname_ = 'updated' + elif nodeName_ == 'entry': + obj_ = entryType.factory() + obj_.build(child_) + self.entry.append(obj_) + obj_.original_tagname_ = 'entry' + else: + obj_ = self.gds_build_any(child_, 'feedType') + if obj_ is not None: + self.add_anytypeobjs_(obj_) +# end class feedType + + +class entryType(GeneratedsSuper): + """The Atom entry construct is defined in section 4.1.2 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, author=None, category=None, content=None, contributor=None, id=None, link=None, published=None, rights=None, source=None, summary=None, title=None, updated=None, anytypeobjs_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + if author is None: + self.author = [] + else: + self.author = author + if category is None: + self.category = [] + else: + self.category = category + if content is None: + self.content = [] + else: + self.content = content + if contributor is None: + self.contributor = [] + else: + self.contributor = contributor + if id is None: + self.id = [] + else: + self.id = id + if link is None: + self.link = [] + else: + self.link = link + if published is None: + self.published = [] + else: + self.published = published + if rights is None: + self.rights = [] + else: + self.rights = rights + if source is None: + self.source = [] + else: + self.source = source + if summary is None: + self.summary = [] + else: + self.summary = summary + if title is None: + self.title = [] + else: + self.title = title + if updated is None: + self.updated = [] + else: + self.updated = updated + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, entryType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if entryType.subclass: + return entryType.subclass(*args_, **kwargs_) + else: + return entryType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_author(self): return self.author + def set_author(self, author): self.author = author + def add_author(self, value): self.author.append(value) + def insert_author_at(self, index, value): self.author.insert(index, value) + def replace_author_at(self, index, value): self.author[index] = value + def get_category(self): return self.category + def set_category(self, category): self.category = category + def add_category(self, value): self.category.append(value) + def insert_category_at(self, index, value): self.category.insert(index, value) + def replace_category_at(self, index, value): self.category[index] = value + def get_content(self): return self.content + def set_content(self, content): self.content = content + def add_content(self, value): self.content.append(value) + def insert_content_at(self, index, value): self.content.insert(index, value) + def replace_content_at(self, index, value): self.content[index] = value + def get_contributor(self): return self.contributor + def set_contributor(self, contributor): self.contributor = contributor + def add_contributor(self, value): self.contributor.append(value) + def insert_contributor_at(self, index, value): self.contributor.insert(index, value) + def replace_contributor_at(self, index, value): self.contributor[index] = value + def get_id(self): return self.id + def set_id(self, id): self.id = id + def add_id(self, value): self.id.append(value) + def insert_id_at(self, index, value): self.id.insert(index, value) + def replace_id_at(self, index, value): self.id[index] = value + def get_link(self): return self.link + def set_link(self, link): self.link = link + def add_link(self, value): self.link.append(value) + def insert_link_at(self, index, value): self.link.insert(index, value) + def replace_link_at(self, index, value): self.link[index] = value + def get_published(self): return self.published + def set_published(self, published): self.published = published + def add_published(self, value): self.published.append(value) + def insert_published_at(self, index, value): self.published.insert(index, value) + def replace_published_at(self, index, value): self.published[index] = value + def get_rights(self): return self.rights + def set_rights(self, rights): self.rights = rights + def add_rights(self, value): self.rights.append(value) + def insert_rights_at(self, index, value): self.rights.insert(index, value) + def replace_rights_at(self, index, value): self.rights[index] = value + def get_source(self): return self.source + def set_source(self, source): self.source = source + def add_source(self, value): self.source.append(value) + def insert_source_at(self, index, value): self.source.insert(index, value) + def replace_source_at(self, index, value): self.source[index] = value + def get_summary(self): return self.summary + def set_summary(self, summary): self.summary = summary + def add_summary(self, value): self.summary.append(value) + def insert_summary_at(self, index, value): self.summary.insert(index, value) + def replace_summary_at(self, index, value): self.summary[index] = value + def get_title(self): return self.title + def set_title(self, title): self.title = title + def add_title(self, value): self.title.append(value) + def insert_title_at(self, index, value): self.title.insert(index, value) + def replace_title_at(self, index, value): self.title[index] = value + def get_updated(self): return self.updated + def set_updated(self, updated): self.updated = updated + def add_updated(self, value): self.updated.append(value) + def insert_updated_at(self, index, value): self.updated.insert(index, value) + def replace_updated_at(self, index, value): self.updated[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def hasContent_(self): + if ( + self.author or + self.category or + self.content or + self.contributor or + self.id or + self.link or + self.published or + self.rights or + self.source or + self.summary or + self.title or + self.updated or + self.anytypeobjs_ + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='entryType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('entryType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='entryType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='entryType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='entryType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='entryType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for author_ in self.author: + author_.export(outfile, level, namespace_, name_='author', pretty_print=pretty_print) + for category_ in self.category: + category_.export(outfile, level, namespace_, name_='category', pretty_print=pretty_print) + for content_ in self.content: + content_.export(outfile, level, namespace_, name_='content', pretty_print=pretty_print) + for contributor_ in self.contributor: + contributor_.export(outfile, level, namespace_, name_='contributor', pretty_print=pretty_print) + for id_ in self.id: + id_.export(outfile, level, namespace_, name_='id', pretty_print=pretty_print) + for link_ in self.link: + link_.export(outfile, level, namespace_, name_='link', pretty_print=pretty_print) + for published_ in self.published: + published_.export(outfile, level, namespace_, name_='published', pretty_print=pretty_print) + for rights_ in self.rights: + rights_.export(outfile, level, namespace_, name_='rights', pretty_print=pretty_print) + for source_ in self.source: + source_.export(outfile, level, namespace_, name_='source', pretty_print=pretty_print) + for summary_ in self.summary: + summary_.export(outfile, level, namespace_, name_='summary', pretty_print=pretty_print) + for title_ in self.title: + title_.export(outfile, level, namespace_, name_='title', pretty_print=pretty_print) + for updated_ in self.updated: + updated_.export(outfile, level, namespace_, name_='updated', pretty_print=pretty_print) + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'author': + obj_ = personType.factory() + obj_.build(child_) + self.author.append(obj_) + obj_.original_tagname_ = 'author' + elif nodeName_ == 'category': + obj_ = categoryType.factory() + obj_.build(child_) + self.category.append(obj_) + obj_.original_tagname_ = 'category' + elif nodeName_ == 'content': + obj_ = contentType.factory() + obj_.build(child_) + self.content.append(obj_) + obj_.original_tagname_ = 'content' + elif nodeName_ == 'contributor': + obj_ = personType.factory() + obj_.build(child_) + self.contributor.append(obj_) + obj_.original_tagname_ = 'contributor' + elif nodeName_ == 'id': + obj_ = idType.factory() + obj_.build(child_) + self.id.append(obj_) + obj_.original_tagname_ = 'id' + elif nodeName_ == 'link': + obj_ = linkType.factory() + obj_.build(child_) + self.link.append(obj_) + obj_.original_tagname_ = 'link' + elif nodeName_ == 'published': + obj_ = dateTimeType.factory() + obj_.build(child_) + self.published.append(obj_) + obj_.original_tagname_ = 'published' + elif nodeName_ == 'rights': + obj_ = textType.factory() + obj_.build(child_) + self.rights.append(obj_) + obj_.original_tagname_ = 'rights' + elif nodeName_ == 'source': + obj_ = textType.factory() + obj_.build(child_) + self.source.append(obj_) + obj_.original_tagname_ = 'source' + elif nodeName_ == 'summary': + obj_ = textType.factory() + obj_.build(child_) + self.summary.append(obj_) + obj_.original_tagname_ = 'summary' + elif nodeName_ == 'title': + obj_ = textType.factory() + obj_.build(child_) + self.title.append(obj_) + obj_.original_tagname_ = 'title' + elif nodeName_ == 'updated': + obj_ = dateTimeType.factory() + obj_.build(child_) + self.updated.append(obj_) + obj_.original_tagname_ = 'updated' + else: + obj_ = self.gds_build_any(child_, 'entryType') + if obj_ is not None: + self.add_anytypeobjs_(obj_) +# end class entryType + + +class contentType(GeneratedsSuper): + """The Atom content construct is defined in section 4.1.3 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, type_=None, src=None, base=None, lang=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.type_ = _cast(None, type_) + self.src = _cast(None, src) + self.base = _cast(None, base) + self.lang = _cast(None, lang) + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, contentType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if contentType.subclass: + return contentType.subclass(*args_, **kwargs_) + else: + return contentType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_type(self): return self.type_ + def set_type(self, type_): self.type_ = type_ + def get_src(self): return self.src + def set_src(self, src): self.src = src + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='contentType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('contentType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='contentType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='contentType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='contentType'): + if self.type_ is not None and 'type_' not in already_processed: + already_processed.add('type_') + outfile.write(' type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.type_), input_name='type')), )) + if self.src is not None and 'src' not in already_processed: + already_processed.add('src') + outfile.write(' src=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.src), input_name='src')), )) + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='contentType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('type', node) + if value is not None and 'type' not in already_processed: + already_processed.add('type') + self.type_ = value + value = find_attr_value_('src', node) + if value is not None and 'src' not in already_processed: + already_processed.add('src') + self.src = value + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class contentType + + +class categoryType(GeneratedsSuper): + """The Atom category construct is defined in section 4.2.2 of the + format spec.""" + subclass = None + superclass = None + def __init__(self, term=None, scheme=None, label=None, base=None, lang=None): + self.original_tagname_ = None + self.term = _cast(None, term) + self.scheme = _cast(None, scheme) + self.label = _cast(None, label) + self.base = _cast(None, base) + self.lang = _cast(None, lang) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, categoryType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if categoryType.subclass: + return categoryType.subclass(*args_, **kwargs_) + else: + return categoryType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_term(self): return self.term + def set_term(self, term): self.term = term + def get_scheme(self): return self.scheme + def set_scheme(self, scheme): self.scheme = scheme + def get_label(self): return self.label + def set_label(self, label): self.label = label + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='categoryType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('categoryType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='categoryType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='categoryType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='categoryType'): + if self.term is not None and 'term' not in already_processed: + already_processed.add('term') + outfile.write(' term=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.term), input_name='term')), )) + if self.scheme is not None and 'scheme' not in already_processed: + already_processed.add('scheme') + outfile.write(' scheme=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.scheme), input_name='scheme')), )) + if self.label is not None and 'label' not in already_processed: + already_processed.add('label') + outfile.write(' label=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.label), input_name='label')), )) + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='categoryType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('term', node) + if value is not None and 'term' not in already_processed: + already_processed.add('term') + self.term = value + value = find_attr_value_('scheme', node) + if value is not None and 'scheme' not in already_processed: + already_processed.add('scheme') + self.scheme = value + value = find_attr_value_('label', node) + if value is not None and 'label' not in already_processed: + already_processed.add('label') + self.label = value + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class categoryType + + +class generatorType(GeneratedsSuper): + """The Atom generator element is defined in section 4.2.4 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, uri=None, version=None, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.uri = _cast(None, uri) + self.version = _cast(None, version) + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, generatorType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if generatorType.subclass: + return generatorType.subclass(*args_, **kwargs_) + else: + return generatorType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_uri(self): return self.uri + def set_uri(self, uri): self.uri = uri + def get_version(self): return self.version + def set_version(self, version): self.version = version + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='generatorType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('generatorType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='generatorType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='generatorType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='generatorType'): + if self.uri is not None and 'uri' not in already_processed: + already_processed.add('uri') + outfile.write(' uri=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.uri), input_name='uri')), )) + if self.version is not None and 'version' not in already_processed: + already_processed.add('version') + outfile.write(' version=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.version), input_name='version')), )) + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='generatorType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('uri', node) + if value is not None and 'uri' not in already_processed: + already_processed.add('uri') + self.uri = value + value = find_attr_value_('version', node) + if value is not None and 'version' not in already_processed: + already_processed.add('version') + self.version = value + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class generatorType + + +class iconType(GeneratedsSuper): + """The Atom icon construct is defined in section 4.2.5 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, iconType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if iconType.subclass: + return iconType.subclass(*args_, **kwargs_) + else: + return iconType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='iconType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('iconType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='iconType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='iconType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='iconType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='iconType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class iconType + + +class idType(GeneratedsSuper): + """The Atom id construct is defined in section 4.2.6 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, idType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if idType.subclass: + return idType.subclass(*args_, **kwargs_) + else: + return idType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='idType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('idType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='idType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='idType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='idType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='idType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class idType + + +class linkType(GeneratedsSuper): + """The Atom link construct is defined in section 3.4 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, href=None, rel=None, type_=None, hreflang=None, title=None, length=None, base=None, lang=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.href = _cast(None, href) + self.rel = _cast(None, rel) + self.type_ = _cast(None, type_) + self.hreflang = _cast(None, hreflang) + self.title = _cast(None, title) + self.length = _cast(int, length) + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, linkType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if linkType.subclass: + return linkType.subclass(*args_, **kwargs_) + else: + return linkType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_href(self): return self.href + def set_href(self, href): self.href = href + def get_rel(self): return self.rel + def set_rel(self, rel): self.rel = rel + def get_type(self): return self.type_ + def set_type(self, type_): self.type_ = type_ + def get_hreflang(self): return self.hreflang + def set_hreflang(self, hreflang): self.hreflang = hreflang + def get_title(self): return self.title + def set_title(self, title): self.title = title + def get_length(self): return self.length + def set_length(self, length): self.length = length + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='linkType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('linkType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='linkType') + outfile.write('>') + self.exportChildren(outfile, level + 1, namespace_, name_, pretty_print=pretty_print) + outfile.write(self.convert_unicode(self.valueOf_)) + outfile.write('%s' % (namespace_, name_, eol_)) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='linkType'): + if self.href is not None and 'href' not in already_processed: + already_processed.add('href') + outfile.write(' href=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.href), input_name='href')), )) + if self.rel is not None and 'rel' not in already_processed: + already_processed.add('rel') + outfile.write(' rel=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.rel), input_name='rel')), )) + if self.type_ is not None and 'type_' not in already_processed: + already_processed.add('type_') + outfile.write(' type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.type_), input_name='type')), )) + if self.hreflang is not None and 'hreflang' not in already_processed: + already_processed.add('hreflang') + outfile.write(' hreflang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.hreflang), input_name='hreflang')), )) + if self.title is not None and 'title' not in already_processed: + already_processed.add('title') + outfile.write(' title=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.title), input_name='title')), )) + if self.length is not None and 'length' not in already_processed: + already_processed.add('length') + outfile.write(' length="%s"' % self.gds_format_integer(self.length, input_name='length')) + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='linkType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('href', node) + if value is not None and 'href' not in already_processed: + already_processed.add('href') + self.href = value + value = find_attr_value_('rel', node) + if value is not None and 'rel' not in already_processed: + already_processed.add('rel') + self.rel = value + value = find_attr_value_('type', node) + if value is not None and 'type' not in already_processed: + already_processed.add('type') + self.type_ = value + value = find_attr_value_('hreflang', node) + if value is not None and 'hreflang' not in already_processed: + already_processed.add('hreflang') + self.hreflang = value + value = find_attr_value_('title', node) + if value is not None and 'title' not in already_processed: + already_processed.add('title') + self.title = value + value = find_attr_value_('length', node) + if value is not None and 'length' not in already_processed: + already_processed.add('length') + try: + self.length = int(value) + except ValueError as exp: + raise_parse_error(node, 'Bad integer attribute: %s' % exp) + if self.length <= 0: + raise_parse_error(node, 'Invalid PositiveInteger') + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) + pass +# end class linkType + + +class logoType(GeneratedsSuper): + """The Atom logo construct is defined in section 4.2.8 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, logoType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if logoType.subclass: + return logoType.subclass(*args_, **kwargs_) + else: + return logoType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='logoType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('logoType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='logoType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='logoType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='logoType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='logoType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class logoType + + +class sourceType(GeneratedsSuper): + """The Atom source construct is defined in section 4.2.11 of the format + spec.""" + subclass = None + superclass = None + def __init__(self, base=None, lang=None, author=None, category=None, contributor=None, generator=None, icon=None, id=None, link=None, logo=None, rights=None, subtitle=None, title=None, updated=None, anytypeobjs_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + if author is None: + self.author = [] + else: + self.author = author + if category is None: + self.category = [] + else: + self.category = category + if contributor is None: + self.contributor = [] + else: + self.contributor = contributor + if generator is None: + self.generator = [] + else: + self.generator = generator + if icon is None: + self.icon = [] + else: + self.icon = icon + if id is None: + self.id = [] + else: + self.id = id + if link is None: + self.link = [] + else: + self.link = link + if logo is None: + self.logo = [] + else: + self.logo = logo + if rights is None: + self.rights = [] + else: + self.rights = rights + if subtitle is None: + self.subtitle = [] + else: + self.subtitle = subtitle + if title is None: + self.title = [] + else: + self.title = title + if updated is None: + self.updated = [] + else: + self.updated = updated + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, sourceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if sourceType.subclass: + return sourceType.subclass(*args_, **kwargs_) + else: + return sourceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_author(self): return self.author + def set_author(self, author): self.author = author + def add_author(self, value): self.author.append(value) + def insert_author_at(self, index, value): self.author.insert(index, value) + def replace_author_at(self, index, value): self.author[index] = value + def get_category(self): return self.category + def set_category(self, category): self.category = category + def add_category(self, value): self.category.append(value) + def insert_category_at(self, index, value): self.category.insert(index, value) + def replace_category_at(self, index, value): self.category[index] = value + def get_contributor(self): return self.contributor + def set_contributor(self, contributor): self.contributor = contributor + def add_contributor(self, value): self.contributor.append(value) + def insert_contributor_at(self, index, value): self.contributor.insert(index, value) + def replace_contributor_at(self, index, value): self.contributor[index] = value + def get_generator(self): return self.generator + def set_generator(self, generator): self.generator = generator + def add_generator(self, value): self.generator.append(value) + def insert_generator_at(self, index, value): self.generator.insert(index, value) + def replace_generator_at(self, index, value): self.generator[index] = value + def get_icon(self): return self.icon + def set_icon(self, icon): self.icon = icon + def add_icon(self, value): self.icon.append(value) + def insert_icon_at(self, index, value): self.icon.insert(index, value) + def replace_icon_at(self, index, value): self.icon[index] = value + def get_id(self): return self.id + def set_id(self, id): self.id = id + def add_id(self, value): self.id.append(value) + def insert_id_at(self, index, value): self.id.insert(index, value) + def replace_id_at(self, index, value): self.id[index] = value + def get_link(self): return self.link + def set_link(self, link): self.link = link + def add_link(self, value): self.link.append(value) + def insert_link_at(self, index, value): self.link.insert(index, value) + def replace_link_at(self, index, value): self.link[index] = value + def get_logo(self): return self.logo + def set_logo(self, logo): self.logo = logo + def add_logo(self, value): self.logo.append(value) + def insert_logo_at(self, index, value): self.logo.insert(index, value) + def replace_logo_at(self, index, value): self.logo[index] = value + def get_rights(self): return self.rights + def set_rights(self, rights): self.rights = rights + def add_rights(self, value): self.rights.append(value) + def insert_rights_at(self, index, value): self.rights.insert(index, value) + def replace_rights_at(self, index, value): self.rights[index] = value + def get_subtitle(self): return self.subtitle + def set_subtitle(self, subtitle): self.subtitle = subtitle + def add_subtitle(self, value): self.subtitle.append(value) + def insert_subtitle_at(self, index, value): self.subtitle.insert(index, value) + def replace_subtitle_at(self, index, value): self.subtitle[index] = value + def get_title(self): return self.title + def set_title(self, title): self.title = title + def add_title(self, value): self.title.append(value) + def insert_title_at(self, index, value): self.title.insert(index, value) + def replace_title_at(self, index, value): self.title[index] = value + def get_updated(self): return self.updated + def set_updated(self, updated): self.updated = updated + def add_updated(self, value): self.updated.append(value) + def insert_updated_at(self, index, value): self.updated.insert(index, value) + def replace_updated_at(self, index, value): self.updated[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def hasContent_(self): + if ( + self.author or + self.category or + self.contributor or + self.generator or + self.icon or + self.id or + self.link or + self.logo or + self.rights or + self.subtitle or + self.title or + self.updated or + self.anytypeobjs_ + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='sourceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('sourceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='sourceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='sourceType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='sourceType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='sourceType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for author_ in self.author: + author_.export(outfile, level, namespace_, name_='author', pretty_print=pretty_print) + for category_ in self.category: + category_.export(outfile, level, namespace_, name_='category', pretty_print=pretty_print) + for contributor_ in self.contributor: + contributor_.export(outfile, level, namespace_, name_='contributor', pretty_print=pretty_print) + for generator_ in self.generator: + generator_.export(outfile, level, namespace_, name_='generator', pretty_print=pretty_print) + for icon_ in self.icon: + icon_.export(outfile, level, namespace_, name_='icon', pretty_print=pretty_print) + for id_ in self.id: + id_.export(outfile, level, namespace_, name_='id', pretty_print=pretty_print) + for link_ in self.link: + link_.export(outfile, level, namespace_, name_='link', pretty_print=pretty_print) + for logo_ in self.logo: + logo_.export(outfile, level, namespace_, name_='logo', pretty_print=pretty_print) + for rights_ in self.rights: + rights_.export(outfile, level, namespace_, name_='rights', pretty_print=pretty_print) + for subtitle_ in self.subtitle: + subtitle_.export(outfile, level, namespace_, name_='subtitle', pretty_print=pretty_print) + for title_ in self.title: + title_.export(outfile, level, namespace_, name_='title', pretty_print=pretty_print) + for updated_ in self.updated: + updated_.export(outfile, level, namespace_, name_='updated', pretty_print=pretty_print) + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'author': + obj_ = personType.factory() + obj_.build(child_) + self.author.append(obj_) + obj_.original_tagname_ = 'author' + elif nodeName_ == 'category': + obj_ = categoryType.factory() + obj_.build(child_) + self.category.append(obj_) + obj_.original_tagname_ = 'category' + elif nodeName_ == 'contributor': + obj_ = personType.factory() + obj_.build(child_) + self.contributor.append(obj_) + obj_.original_tagname_ = 'contributor' + elif nodeName_ == 'generator': + obj_ = generatorType.factory() + obj_.build(child_) + self.generator.append(obj_) + obj_.original_tagname_ = 'generator' + elif nodeName_ == 'icon': + obj_ = iconType.factory() + obj_.build(child_) + self.icon.append(obj_) + obj_.original_tagname_ = 'icon' + elif nodeName_ == 'id': + obj_ = idType.factory() + obj_.build(child_) + self.id.append(obj_) + obj_.original_tagname_ = 'id' + elif nodeName_ == 'link': + obj_ = linkType.factory() + obj_.build(child_) + self.link.append(obj_) + obj_.original_tagname_ = 'link' + elif nodeName_ == 'logo': + obj_ = logoType.factory() + obj_.build(child_) + self.logo.append(obj_) + obj_.original_tagname_ = 'logo' + elif nodeName_ == 'rights': + obj_ = textType.factory() + obj_.build(child_) + self.rights.append(obj_) + obj_.original_tagname_ = 'rights' + elif nodeName_ == 'subtitle': + obj_ = textType.factory() + obj_.build(child_) + self.subtitle.append(obj_) + obj_.original_tagname_ = 'subtitle' + elif nodeName_ == 'title': + obj_ = textType.factory() + obj_.build(child_) + self.title.append(obj_) + obj_.original_tagname_ = 'title' + elif nodeName_ == 'updated': + obj_ = dateTimeType.factory() + obj_.build(child_) + self.updated.append(obj_) + obj_.original_tagname_ = 'updated' + else: + obj_ = self.gds_build_any(child_, 'sourceType') + if obj_ is not None: + self.add_anytypeobjs_(obj_) +# end class sourceType + + +class uriType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, uriType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if uriType.subclass: + return uriType.subclass(*args_, **kwargs_) + else: + return uriType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='uriType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('uriType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='uriType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='uriType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='uriType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='uriType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class uriType + + +class dateTimeType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, base=None, lang=None, valueOf_=None): + self.original_tagname_ = None + self.base = _cast(None, base) + self.lang = _cast(None, lang) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, dateTimeType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if dateTimeType.subclass: + return dateTimeType.subclass(*args_, **kwargs_) + else: + return dateTimeType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_base(self): return self.base + def set_base(self, base): self.base = base + def get_lang(self): return self.lang + def set_lang(self, lang): self.lang = lang + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='atom:', name_='dateTimeType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:atom="http://www.w3.org/2005/Atom"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('dateTimeType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='dateTimeType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='atom:', name_='dateTimeType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='atom:', name_='dateTimeType'): + if self.base is not None and 'base' not in already_processed: + already_processed.add('base') + outfile.write(' base=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.base), input_name='base')), )) + if self.lang is not None and 'lang' not in already_processed: + already_processed.add('lang') + outfile.write(' lang=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.lang), input_name='lang')), )) + def exportChildren(self, outfile, level, namespace_='atom:', name_='dateTimeType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('base', node) + if value is not None and 'base' not in already_processed: + already_processed.add('base') + self.base = value + value = find_attr_value_('lang', node) + if value is not None and 'lang' not in already_processed: + already_processed.add('lang') + self.lang = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class dateTimeType + + +class ECKeyValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, ECParameters=None, NamedCurve=None, PublicKey=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.ECParameters = ECParameters + self.NamedCurve = NamedCurve + self.PublicKey = PublicKey + self.validate_ECPointType(self.PublicKey) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ECKeyValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ECKeyValueType.subclass: + return ECKeyValueType.subclass(*args_, **kwargs_) + else: + return ECKeyValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_ECParameters(self): return self.ECParameters + def set_ECParameters(self, ECParameters): self.ECParameters = ECParameters + def get_NamedCurve(self): return self.NamedCurve + def set_NamedCurve(self, NamedCurve): self.NamedCurve = NamedCurve + def get_PublicKey(self): return self.PublicKey + def set_PublicKey(self, PublicKey): self.PublicKey = PublicKey + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def validate_ECPointType(self, value): + # Validate type ECPointType, a restriction on ds:CryptoBinary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.ECParameters is not None or + self.NamedCurve is not None or + self.PublicKey is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='ECKeyValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ECKeyValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ECKeyValueType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='ECKeyValueType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='ECKeyValueType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='ECKeyValueType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.ECParameters is not None: + self.ECParameters.export(outfile, level, namespace_, name_='ECParameters', pretty_print=pretty_print) + if self.NamedCurve is not None: + self.NamedCurve.export(outfile, level, namespace_, name_='NamedCurve', pretty_print=pretty_print) + if self.PublicKey is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.PublicKey, input_name='PublicKey'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'ECParameters': + obj_ = ECParametersType.factory() + obj_.build(child_) + self.ECParameters = obj_ + obj_.original_tagname_ = 'ECParameters' + elif nodeName_ == 'NamedCurve': + obj_ = NamedCurveType.factory() + obj_.build(child_) + self.NamedCurve = obj_ + obj_.original_tagname_ = 'NamedCurve' + elif nodeName_ == 'PublicKey': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'PublicKey') + else: + bval_ = None + self.PublicKey = bval_ + # validate type ECPointType + self.validate_ECPointType(self.PublicKey) +# end class ECKeyValueType + + +class NamedCurveType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, URI=None): + self.original_tagname_ = None + self.URI = _cast(None, URI) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, NamedCurveType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if NamedCurveType.subclass: + return NamedCurveType.subclass(*args_, **kwargs_) + else: + return NamedCurveType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_URI(self): return self.URI + def set_URI(self, URI): self.URI = URI + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='NamedCurveType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('NamedCurveType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='NamedCurveType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='NamedCurveType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='NamedCurveType'): + if self.URI is not None and 'URI' not in already_processed: + already_processed.add('URI') + outfile.write(' URI=%s' % (quote_attrib(self.URI), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='NamedCurveType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('URI', node) + if value is not None and 'URI' not in already_processed: + already_processed.add('URI') + self.URI = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class NamedCurveType + + +class ECParametersType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, FieldID=None, Curve=None, Base=None, Order=None, CoFactor=None, ValidationData=None): + self.original_tagname_ = None + self.FieldID = FieldID + self.Curve = Curve + self.Base = Base + self.validate_ECPointType(self.Base) + self.Order = Order + self.validate_CryptoBinary(self.Order) + self.CoFactor = CoFactor + self.ValidationData = ValidationData + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ECParametersType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ECParametersType.subclass: + return ECParametersType.subclass(*args_, **kwargs_) + else: + return ECParametersType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_FieldID(self): return self.FieldID + def set_FieldID(self, FieldID): self.FieldID = FieldID + def get_Curve(self): return self.Curve + def set_Curve(self, Curve): self.Curve = Curve + def get_Base(self): return self.Base + def set_Base(self, Base): self.Base = Base + def get_Order(self): return self.Order + def set_Order(self, Order): self.Order = Order + def get_CoFactor(self): return self.CoFactor + def set_CoFactor(self, CoFactor): self.CoFactor = CoFactor + def get_ValidationData(self): return self.ValidationData + def set_ValidationData(self, ValidationData): self.ValidationData = ValidationData + def validate_ECPointType(self, value): + # Validate type ECPointType, a restriction on ds:CryptoBinary. + if value is not None and Validate_simpletypes_: + pass + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.FieldID is not None or + self.Curve is not None or + self.Base is not None or + self.Order is not None or + self.CoFactor is not None or + self.ValidationData is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='ECParametersType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ECParametersType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ECParametersType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='ECParametersType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='ECParametersType'): + pass + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='ECParametersType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.FieldID is not None: + self.FieldID.export(outfile, level, namespace_, name_='FieldID', pretty_print=pretty_print) + if self.Curve is not None: + self.Curve.export(outfile, level, namespace_, name_='Curve', pretty_print=pretty_print) + if self.Base is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Base, input_name='Base'), eol_)) + if self.Order is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Order, input_name='Order'), eol_)) + if self.CoFactor is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.CoFactor), input_name='CoFactor')), eol_)) + if self.ValidationData is not None: + self.ValidationData.export(outfile, level, namespace_, name_='ValidationData', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'FieldID': + obj_ = FieldIDType.factory() + obj_.build(child_) + self.FieldID = obj_ + obj_.original_tagname_ = 'FieldID' + elif nodeName_ == 'Curve': + obj_ = CurveType.factory() + obj_.build(child_) + self.Curve = obj_ + obj_.original_tagname_ = 'Curve' + elif nodeName_ == 'Base': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Base') + else: + bval_ = None + self.Base = bval_ + # validate type ECPointType + self.validate_ECPointType(self.Base) + elif nodeName_ == 'Order': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Order') + else: + bval_ = None + self.Order = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Order) + elif nodeName_ == 'CoFactor': + CoFactor_ = child_.text + CoFactor_ = self.gds_validate_string(CoFactor_, node, 'CoFactor') + self.CoFactor = CoFactor_ + elif nodeName_ == 'ValidationData': + obj_ = ECValidationDataType.factory() + obj_.build(child_) + self.ValidationData = obj_ + obj_.original_tagname_ = 'ValidationData' +# end class ECParametersType + + +class FieldIDType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Prime=None, TnB=None, PnB=None, GnB=None, anytypeobjs_=None): + self.original_tagname_ = None + self.Prime = Prime + self.TnB = TnB + self.PnB = PnB + self.GnB = GnB + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, FieldIDType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if FieldIDType.subclass: + return FieldIDType.subclass(*args_, **kwargs_) + else: + return FieldIDType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Prime(self): return self.Prime + def set_Prime(self, Prime): self.Prime = Prime + def get_TnB(self): return self.TnB + def set_TnB(self, TnB): self.TnB = TnB + def get_PnB(self): return self.PnB + def set_PnB(self, PnB): self.PnB = PnB + def get_GnB(self): return self.GnB + def set_GnB(self, GnB): self.GnB = GnB + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def hasContent_(self): + if ( + self.Prime is not None or + self.TnB is not None or + self.PnB is not None or + self.GnB is not None or + self.anytypeobjs_ is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='FieldIDType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('FieldIDType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='FieldIDType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='FieldIDType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='FieldIDType'): + pass + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='FieldIDType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Prime is not None: + self.Prime.export(outfile, level, namespace_='dsig11:', name_='Prime', pretty_print=pretty_print) + if self.TnB is not None: + self.TnB.export(outfile, level, namespace_='dsig11:', name_='TnB', pretty_print=pretty_print) + if self.PnB is not None: + self.PnB.export(outfile, level, namespace_='dsig11:', name_='PnB', pretty_print=pretty_print) + if self.GnB is not None: + self.GnB.export(outfile, level, namespace_='dsig11:', name_='GnB', pretty_print=pretty_print) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Prime': + obj_ = PrimeFieldParamsType.factory() + obj_.build(child_) + self.Prime = obj_ + obj_.original_tagname_ = 'Prime' + elif nodeName_ == 'TnB': + obj_ = TnBFieldParamsType.factory() + obj_.build(child_) + self.TnB = obj_ + obj_.original_tagname_ = 'TnB' + elif nodeName_ == 'PnB': + obj_ = PnBFieldParamsType.factory() + obj_.build(child_) + self.PnB = obj_ + obj_.original_tagname_ = 'PnB' + elif nodeName_ == 'GnB': + class_obj_ = self.get_class_obj_(child_, CharTwoFieldParamsType) + obj_ = class_obj_.factory() + obj_.build(child_) + self.GnB = obj_ + obj_.original_tagname_ = 'GnB' + else: + obj_ = self.gds_build_any(child_, 'FieldIDType') + if obj_ is not None: + self.set_anytypeobjs_(obj_) +# end class FieldIDType + + +class CurveType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, A=None, B=None): + self.original_tagname_ = None + self.A = A + self.validate_CryptoBinary(self.A) + self.B = B + self.validate_CryptoBinary(self.B) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, CurveType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if CurveType.subclass: + return CurveType.subclass(*args_, **kwargs_) + else: + return CurveType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_A(self): return self.A + def set_A(self, A): self.A = A + def get_B(self): return self.B + def set_B(self, B): self.B = B + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.A is not None or + self.B is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='CurveType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('CurveType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='CurveType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='CurveType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='CurveType'): + pass + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='CurveType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.A is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.A, input_name='A'), eol_)) + if self.B is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.B, input_name='B'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'A': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'A') + else: + bval_ = None + self.A = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.A) + elif nodeName_ == 'B': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'B') + else: + bval_ = None + self.B = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.B) +# end class CurveType + + +class ECValidationDataType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, hashAlgorithm=None, seed=None): + self.original_tagname_ = None + self.hashAlgorithm = _cast(None, hashAlgorithm) + self.seed = seed + self.validate_CryptoBinary(self.seed) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ECValidationDataType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ECValidationDataType.subclass: + return ECValidationDataType.subclass(*args_, **kwargs_) + else: + return ECValidationDataType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_seed(self): return self.seed + def set_seed(self, seed): self.seed = seed + def get_hashAlgorithm(self): return self.hashAlgorithm + def set_hashAlgorithm(self, hashAlgorithm): self.hashAlgorithm = hashAlgorithm + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.seed is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='ECValidationDataType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ECValidationDataType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ECValidationDataType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='ECValidationDataType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='ECValidationDataType'): + if self.hashAlgorithm is not None and 'hashAlgorithm' not in already_processed: + already_processed.add('hashAlgorithm') + outfile.write(' hashAlgorithm=%s' % (quote_attrib(self.hashAlgorithm), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='ECValidationDataType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.seed is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.seed, input_name='seed'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('hashAlgorithm', node) + if value is not None and 'hashAlgorithm' not in already_processed: + already_processed.add('hashAlgorithm') + self.hashAlgorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'seed': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'seed') + else: + bval_ = None + self.seed = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.seed) +# end class ECValidationDataType + + +class PrimeFieldParamsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, P=None): + self.original_tagname_ = None + self.P = P + self.validate_CryptoBinary(self.P) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PrimeFieldParamsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PrimeFieldParamsType.subclass: + return PrimeFieldParamsType.subclass(*args_, **kwargs_) + else: + return PrimeFieldParamsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_P(self): return self.P + def set_P(self, P): self.P = P + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.P is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='PrimeFieldParamsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PrimeFieldParamsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PrimeFieldParamsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='PrimeFieldParamsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='PrimeFieldParamsType'): + pass + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='PrimeFieldParamsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.P is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.P, input_name='P'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'P': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'P') + else: + bval_ = None + self.P = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.P) +# end class PrimeFieldParamsType + + +class CharTwoFieldParamsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, M=None, extensiontype_=None): + self.original_tagname_ = None + self.M = M + self.extensiontype_ = extensiontype_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, CharTwoFieldParamsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if CharTwoFieldParamsType.subclass: + return CharTwoFieldParamsType.subclass(*args_, **kwargs_) + else: + return CharTwoFieldParamsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_M(self): return self.M + def set_M(self, M): self.M = M + def get_extensiontype_(self): return self.extensiontype_ + def set_extensiontype_(self, extensiontype_): self.extensiontype_ = extensiontype_ + def hasContent_(self): + if ( + self.M is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='CharTwoFieldParamsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('CharTwoFieldParamsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='CharTwoFieldParamsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='CharTwoFieldParamsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='CharTwoFieldParamsType'): + if self.extensiontype_ is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + outfile.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') + outfile.write(' xsi:type="%s"' % self.extensiontype_) + pass + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='CharTwoFieldParamsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.M is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.M), input_name='M')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('xsi:type', node) + if value is not None and 'xsi:type' not in already_processed: + already_processed.add('xsi:type') + self.extensiontype_ = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'M': + M_ = child_.text + M_ = self.gds_validate_string(M_, node, 'M') + self.M = M_ +# end class CharTwoFieldParamsType + + +class TnBFieldParamsType(CharTwoFieldParamsType): + subclass = None + superclass = CharTwoFieldParamsType + def __init__(self, M=None, K=None): + self.original_tagname_ = None + super(TnBFieldParamsType, self).__init__(M, ) + self.K = K + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, TnBFieldParamsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if TnBFieldParamsType.subclass: + return TnBFieldParamsType.subclass(*args_, **kwargs_) + else: + return TnBFieldParamsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_K(self): return self.K + def set_K(self, K): self.K = K + def hasContent_(self): + if ( + self.K is not None or + super(TnBFieldParamsType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='TnBFieldParamsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('TnBFieldParamsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='TnBFieldParamsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='TnBFieldParamsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='TnBFieldParamsType'): + super(TnBFieldParamsType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='TnBFieldParamsType') + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='TnBFieldParamsType', fromsubclass_=False, pretty_print=True): + super(TnBFieldParamsType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.K is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.K), input_name='K')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(TnBFieldParamsType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'K': + K_ = child_.text + K_ = self.gds_validate_string(K_, node, 'K') + self.K = K_ + super(TnBFieldParamsType, self).buildChildren(child_, node, nodeName_, True) +# end class TnBFieldParamsType + + +class PnBFieldParamsType(CharTwoFieldParamsType): + subclass = None + superclass = CharTwoFieldParamsType + def __init__(self, M=None, K1=None, K2=None, K3=None): + self.original_tagname_ = None + super(PnBFieldParamsType, self).__init__(M, ) + self.K1 = K1 + self.K2 = K2 + self.K3 = K3 + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PnBFieldParamsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PnBFieldParamsType.subclass: + return PnBFieldParamsType.subclass(*args_, **kwargs_) + else: + return PnBFieldParamsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_K1(self): return self.K1 + def set_K1(self, K1): self.K1 = K1 + def get_K2(self): return self.K2 + def set_K2(self, K2): self.K2 = K2 + def get_K3(self): return self.K3 + def set_K3(self, K3): self.K3 = K3 + def hasContent_(self): + if ( + self.K1 is not None or + self.K2 is not None or + self.K3 is not None or + super(PnBFieldParamsType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='PnBFieldParamsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PnBFieldParamsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PnBFieldParamsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='PnBFieldParamsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='PnBFieldParamsType'): + super(PnBFieldParamsType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='PnBFieldParamsType') + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='PnBFieldParamsType', fromsubclass_=False, pretty_print=True): + super(PnBFieldParamsType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.K1 is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.K1), input_name='K1')), eol_)) + if self.K2 is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.K2), input_name='K2')), eol_)) + if self.K3 is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.K3), input_name='K3')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(PnBFieldParamsType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'K1': + K1_ = child_.text + K1_ = self.gds_validate_string(K1_, node, 'K1') + self.K1 = K1_ + elif nodeName_ == 'K2': + K2_ = child_.text + K2_ = self.gds_validate_string(K2_, node, 'K2') + self.K2 = K2_ + elif nodeName_ == 'K3': + K3_ = child_.text + K3_ = self.gds_validate_string(K3_, node, 'K3') + self.K3 = K3_ + super(PnBFieldParamsType, self).buildChildren(child_, node, nodeName_, True) +# end class PnBFieldParamsType + + +class DEREncodedKeyValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, valueOf_=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, DEREncodedKeyValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if DEREncodedKeyValueType.subclass: + return DEREncodedKeyValueType.subclass(*args_, **kwargs_) + else: + return DEREncodedKeyValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='DEREncodedKeyValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('DEREncodedKeyValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='DEREncodedKeyValueType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='DEREncodedKeyValueType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='DEREncodedKeyValueType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='DEREncodedKeyValueType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class DEREncodedKeyValueType + + +class KeyInfoReferenceType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, URI=None, Id=None): + self.original_tagname_ = None + self.URI = _cast(None, URI) + self.Id = _cast(None, Id) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, KeyInfoReferenceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if KeyInfoReferenceType.subclass: + return KeyInfoReferenceType.subclass(*args_, **kwargs_) + else: + return KeyInfoReferenceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_URI(self): return self.URI + def set_URI(self, URI): self.URI = URI + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='KeyInfoReferenceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('KeyInfoReferenceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='KeyInfoReferenceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='KeyInfoReferenceType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='KeyInfoReferenceType'): + if self.URI is not None and 'URI' not in already_processed: + already_processed.add('URI') + outfile.write(' URI=%s' % (quote_attrib(self.URI), )) + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='KeyInfoReferenceType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('URI', node) + if value is not None and 'URI' not in already_processed: + already_processed.add('URI') + self.URI = value + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class KeyInfoReferenceType + + +class X509DigestType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Algorithm=None, valueOf_=None): + self.original_tagname_ = None + self.Algorithm = _cast(None, Algorithm) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, X509DigestType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if X509DigestType.subclass: + return X509DigestType.subclass(*args_, **kwargs_) + else: + return X509DigestType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Algorithm(self): return self.Algorithm + def set_Algorithm(self, Algorithm): self.Algorithm = Algorithm + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='dsig11:', name_='X509DigestType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:dsig11="http://www.w3.org/2009/xmldsig11#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('X509DigestType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='X509DigestType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='dsig11:', name_='X509DigestType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='dsig11:', name_='X509DigestType'): + if self.Algorithm is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + outfile.write(' Algorithm=%s' % (quote_attrib(self.Algorithm), )) + def exportChildren(self, outfile, level, namespace_='dsig11:', name_='X509DigestType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Algorithm', node) + if value is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + self.Algorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class X509DigestType + + +class SignatureType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, SignedInfo=None, SignatureValue=None, KeyInfo=None, Object=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.SignedInfo = SignedInfo + self.SignatureValue = SignatureValue + self.KeyInfo = KeyInfo + if Object is None: + self.Object = [] + else: + self.Object = Object + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignatureType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignatureType.subclass: + return SignatureType.subclass(*args_, **kwargs_) + else: + return SignatureType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_SignedInfo(self): return self.SignedInfo + def set_SignedInfo(self, SignedInfo): self.SignedInfo = SignedInfo + def get_SignatureValue(self): return self.SignatureValue + def set_SignatureValue(self, SignatureValue): self.SignatureValue = SignatureValue + def get_KeyInfo(self): return self.KeyInfo + def set_KeyInfo(self, KeyInfo): self.KeyInfo = KeyInfo + def get_Object(self): return self.Object + def set_Object(self, Object): self.Object = Object + def add_Object(self, value): self.Object.append(value) + def insert_Object_at(self, index, value): self.Object.insert(index, value) + def replace_Object_at(self, index, value): self.Object[index] = value + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + self.SignedInfo is not None or + self.SignatureValue is not None or + self.KeyInfo is not None or + self.Object + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignatureType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignatureType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignatureType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignatureType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignatureType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignatureType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.SignedInfo is not None: + self.SignedInfo.export(outfile, level, namespace_='ds:', name_='SignedInfo', pretty_print=pretty_print) + if self.SignatureValue is not None: + self.SignatureValue.export(outfile, level, namespace_='ds:', name_='SignatureValue', pretty_print=pretty_print) + if self.KeyInfo is not None: + self.KeyInfo.export(outfile, level, namespace_='ds:', name_='KeyInfo', pretty_print=pretty_print) + for Object_ in self.Object: + Object_.export(outfile, level, namespace_='ds:', name_='Object', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'SignedInfo': + obj_ = SignedInfoType.factory() + obj_.build(child_) + self.SignedInfo = obj_ + obj_.original_tagname_ = 'SignedInfo' + elif nodeName_ == 'SignatureValue': + obj_ = SignatureValueType.factory() + obj_.build(child_) + self.SignatureValue = obj_ + obj_.original_tagname_ = 'SignatureValue' + elif nodeName_ == 'KeyInfo': + obj_ = KeyInfoType.factory() + obj_.build(child_) + self.KeyInfo = obj_ + obj_.original_tagname_ = 'KeyInfo' + elif nodeName_ == 'Object': + obj_ = ObjectType.factory() + obj_.build(child_) + self.Object.append(obj_) + obj_.original_tagname_ = 'Object' +# end class SignatureType + + +class SignatureValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, valueOf_=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignatureValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignatureValueType.subclass: + return SignatureValueType.subclass(*args_, **kwargs_) + else: + return SignatureValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignatureValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignatureValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignatureValueType') + if self.hasContent_(): + outfile.write('>') + outfile.write(self.convert_unicode(self.valueOf_)) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignatureValueType', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignatureValueType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignatureValueType', fromsubclass_=False, pretty_print=True): + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + pass +# end class SignatureValueType + + +class SignedInfoType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, CanonicalizationMethod=None, SignatureMethod=None, Reference=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.CanonicalizationMethod = CanonicalizationMethod + self.SignatureMethod = SignatureMethod + if Reference is None: + self.Reference = [] + else: + self.Reference = Reference + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignedInfoType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignedInfoType.subclass: + return SignedInfoType.subclass(*args_, **kwargs_) + else: + return SignedInfoType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_CanonicalizationMethod(self): return self.CanonicalizationMethod + def set_CanonicalizationMethod(self, CanonicalizationMethod): self.CanonicalizationMethod = CanonicalizationMethod + def get_SignatureMethod(self): return self.SignatureMethod + def set_SignatureMethod(self, SignatureMethod): self.SignatureMethod = SignatureMethod + def get_Reference(self): return self.Reference + def set_Reference(self, Reference): self.Reference = Reference + def add_Reference(self, value): self.Reference.append(value) + def insert_Reference_at(self, index, value): self.Reference.insert(index, value) + def replace_Reference_at(self, index, value): self.Reference[index] = value + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + self.CanonicalizationMethod is not None or + self.SignatureMethod is not None or + self.Reference + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignedInfoType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignedInfoType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignedInfoType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignedInfoType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignedInfoType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignedInfoType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.CanonicalizationMethod is not None: + self.CanonicalizationMethod.export(outfile, level, namespace_='ds:', name_='CanonicalizationMethod', pretty_print=pretty_print) + if self.SignatureMethod is not None: + self.SignatureMethod.export(outfile, level, namespace_='ds:', name_='SignatureMethod', pretty_print=pretty_print) + for Reference_ in self.Reference: + Reference_.export(outfile, level, namespace_='ds:', name_='Reference', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'CanonicalizationMethod': + obj_ = CanonicalizationMethodType.factory() + obj_.build(child_) + self.CanonicalizationMethod = obj_ + obj_.original_tagname_ = 'CanonicalizationMethod' + elif nodeName_ == 'SignatureMethod': + obj_ = SignatureMethodType.factory() + obj_.build(child_) + self.SignatureMethod = obj_ + obj_.original_tagname_ = 'SignatureMethod' + elif nodeName_ == 'Reference': + obj_ = ReferenceType.factory() + obj_.build(child_) + self.Reference.append(obj_) + obj_.original_tagname_ = 'Reference' +# end class SignedInfoType + + +class CanonicalizationMethodType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Algorithm=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Algorithm = _cast(None, Algorithm) + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, CanonicalizationMethodType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if CanonicalizationMethodType.subclass: + return CanonicalizationMethodType.subclass(*args_, **kwargs_) + else: + return CanonicalizationMethodType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_Algorithm(self): return self.Algorithm + def set_Algorithm(self, Algorithm): self.Algorithm = Algorithm + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='CanonicalizationMethodType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('CanonicalizationMethodType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='CanonicalizationMethodType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='CanonicalizationMethodType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='CanonicalizationMethodType'): + if self.Algorithm is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + outfile.write(' Algorithm=%s' % (quote_attrib(self.Algorithm), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='CanonicalizationMethodType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Algorithm', node) + if value is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + self.Algorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class CanonicalizationMethodType + + +class SignatureMethodType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Algorithm=None, HMACOutputLength=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Algorithm = _cast(None, Algorithm) + self.HMACOutputLength = HMACOutputLength + self.validate_HMACOutputLengthType(self.HMACOutputLength) + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignatureMethodType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignatureMethodType.subclass: + return SignatureMethodType.subclass(*args_, **kwargs_) + else: + return SignatureMethodType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_HMACOutputLength(self): return self.HMACOutputLength + def set_HMACOutputLength(self, HMACOutputLength): self.HMACOutputLength = HMACOutputLength + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_Algorithm(self): return self.Algorithm + def set_Algorithm(self, Algorithm): self.Algorithm = Algorithm + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def validate_HMACOutputLengthType(self, value): + # Validate type HMACOutputLengthType, a restriction on integer. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.HMACOutputLength is not None or + self.anytypeobjs_ or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignatureMethodType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignatureMethodType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignatureMethodType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignatureMethodType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignatureMethodType'): + if self.Algorithm is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + outfile.write(' Algorithm=%s' % (quote_attrib(self.Algorithm), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignatureMethodType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.HMACOutputLength is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.HMACOutputLength, input_name='HMACOutputLength'), eol_)) + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Algorithm', node) + if value is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + self.Algorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'HMACOutputLength' and child_.text is not None: + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + obj_ = self.mixedclass_(MixedContainer.CategorySimple, + MixedContainer.TypeInteger, 'HMACOutputLength', ival_) + self.content_.append(obj_) + elif nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class SignatureMethodType + + +class ReferenceType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, URI=None, Type=None, Transforms=None, DigestMethod=None, DigestValue=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.URI = _cast(None, URI) + self.Type = _cast(None, Type) + self.Transforms = Transforms + self.DigestMethod = DigestMethod + self.DigestValue = DigestValue + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReferenceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReferenceType.subclass: + return ReferenceType.subclass(*args_, **kwargs_) + else: + return ReferenceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Transforms(self): return self.Transforms + def set_Transforms(self, Transforms): self.Transforms = Transforms + def get_DigestMethod(self): return self.DigestMethod + def set_DigestMethod(self, DigestMethod): self.DigestMethod = DigestMethod + def get_DigestValue(self): return self.DigestValue + def set_DigestValue(self, DigestValue): self.DigestValue = DigestValue + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_URI(self): return self.URI + def set_URI(self, URI): self.URI = URI + def get_Type(self): return self.Type + def set_Type(self, Type): self.Type = Type + def hasContent_(self): + if ( + self.Transforms is not None or + self.DigestMethod is not None or + self.DigestValue is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='ReferenceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReferenceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReferenceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='ReferenceType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='ReferenceType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + if self.URI is not None and 'URI' not in already_processed: + already_processed.add('URI') + outfile.write(' URI=%s' % (quote_attrib(self.URI), )) + if self.Type is not None and 'Type' not in already_processed: + already_processed.add('Type') + outfile.write(' Type=%s' % (quote_attrib(self.Type), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='ReferenceType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Transforms is not None: + self.Transforms.export(outfile, level, namespace_='ds:', name_='Transforms', pretty_print=pretty_print) + if self.DigestMethod is not None: + self.DigestMethod.export(outfile, level, namespace_='ds:', name_='DigestMethod', pretty_print=pretty_print) + if self.DigestValue is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.DigestValue, input_name='DigestValue'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + value = find_attr_value_('URI', node) + if value is not None and 'URI' not in already_processed: + already_processed.add('URI') + self.URI = value + value = find_attr_value_('Type', node) + if value is not None and 'Type' not in already_processed: + already_processed.add('Type') + self.Type = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Transforms': + obj_ = TransformsType.factory() + obj_.build(child_) + self.Transforms = obj_ + obj_.original_tagname_ = 'Transforms' + elif nodeName_ == 'DigestMethod': + obj_ = DigestMethodType.factory() + obj_.build(child_) + self.DigestMethod = obj_ + obj_.original_tagname_ = 'DigestMethod' + elif nodeName_ == 'DigestValue': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'DigestValue') + else: + bval_ = None + self.DigestValue = bval_ +# end class ReferenceType + + +class TransformsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Transform=None): + self.original_tagname_ = None + if Transform is None: + self.Transform = [] + else: + self.Transform = Transform + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, TransformsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if TransformsType.subclass: + return TransformsType.subclass(*args_, **kwargs_) + else: + return TransformsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Transform(self): return self.Transform + def set_Transform(self, Transform): self.Transform = Transform + def add_Transform(self, value): self.Transform.append(value) + def insert_Transform_at(self, index, value): self.Transform.insert(index, value) + def replace_Transform_at(self, index, value): self.Transform[index] = value + def hasContent_(self): + if ( + self.Transform + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='TransformsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('TransformsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='TransformsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='TransformsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='TransformsType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='TransformsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for Transform_ in self.Transform: + Transform_.export(outfile, level, namespace_='ds:', name_='Transform', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Transform': + obj_ = TransformType.factory() + obj_.build(child_) + self.Transform.append(obj_) + obj_.original_tagname_ = 'Transform' +# end class TransformsType + + +class TransformType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Algorithm=None, anytypeobjs_=None, XPath=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Algorithm = _cast(None, Algorithm) + self.anytypeobjs_ = anytypeobjs_ + if XPath is None: + self.XPath = [] + else: + self.XPath = XPath + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, TransformType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if TransformType.subclass: + return TransformType.subclass(*args_, **kwargs_) + else: + return TransformType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_XPath(self): return self.XPath + def set_XPath(self, XPath): self.XPath = XPath + def add_XPath(self, value): self.XPath.append(value) + def insert_XPath_at(self, index, value): self.XPath.insert(index, value) + def replace_XPath_at(self, index, value): self.XPath[index] = value + def get_Algorithm(self): return self.Algorithm + def set_Algorithm(self, Algorithm): self.Algorithm = Algorithm + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ is not None or + self.XPath or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='TransformType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('TransformType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='TransformType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='TransformType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='TransformType'): + if self.Algorithm is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + outfile.write(' Algorithm=%s' % (quote_attrib(self.Algorithm), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='TransformType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for XPath_ in self.XPath: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(XPath_), input_name='XPath')), eol_)) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Algorithm', node) + if value is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + self.Algorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + elif nodeName_ == 'XPath' and child_.text is not None: + valuestr_ = child_.text + obj_ = self.mixedclass_(MixedContainer.CategorySimple, + MixedContainer.TypeString, 'XPath', valuestr_) + self.content_.append(obj_) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class TransformType + + +class DigestMethodType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Algorithm=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Algorithm = _cast(None, Algorithm) + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, DigestMethodType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if DigestMethodType.subclass: + return DigestMethodType.subclass(*args_, **kwargs_) + else: + return DigestMethodType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def get_Algorithm(self): return self.Algorithm + def set_Algorithm(self, Algorithm): self.Algorithm = Algorithm + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='DigestMethodType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('DigestMethodType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='DigestMethodType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='DigestMethodType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='DigestMethodType'): + if self.Algorithm is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + outfile.write(' Algorithm=%s' % (quote_attrib(self.Algorithm), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='DigestMethodType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Algorithm', node) + if value is not None and 'Algorithm' not in already_processed: + already_processed.add('Algorithm') + self.Algorithm = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class DigestMethodType + + +class KeyInfoType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, KeyName=None, KeyValue=None, RetrievalMethod=None, X509Data=None, PGPData=None, SPKIData=None, MgmtData=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + if KeyName is None: + self.KeyName = [] + else: + self.KeyName = KeyName + if KeyValue is None: + self.KeyValue = [] + else: + self.KeyValue = KeyValue + if RetrievalMethod is None: + self.RetrievalMethod = [] + else: + self.RetrievalMethod = RetrievalMethod + if X509Data is None: + self.X509Data = [] + else: + self.X509Data = X509Data + if PGPData is None: + self.PGPData = [] + else: + self.PGPData = PGPData + if SPKIData is None: + self.SPKIData = [] + else: + self.SPKIData = SPKIData + if MgmtData is None: + self.MgmtData = [] + else: + self.MgmtData = MgmtData + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, KeyInfoType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if KeyInfoType.subclass: + return KeyInfoType.subclass(*args_, **kwargs_) + else: + return KeyInfoType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_KeyName(self): return self.KeyName + def set_KeyName(self, KeyName): self.KeyName = KeyName + def add_KeyName(self, value): self.KeyName.append(value) + def insert_KeyName_at(self, index, value): self.KeyName.insert(index, value) + def replace_KeyName_at(self, index, value): self.KeyName[index] = value + def get_KeyValue(self): return self.KeyValue + def set_KeyValue(self, KeyValue): self.KeyValue = KeyValue + def add_KeyValue(self, value): self.KeyValue.append(value) + def insert_KeyValue_at(self, index, value): self.KeyValue.insert(index, value) + def replace_KeyValue_at(self, index, value): self.KeyValue[index] = value + def get_RetrievalMethod(self): return self.RetrievalMethod + def set_RetrievalMethod(self, RetrievalMethod): self.RetrievalMethod = RetrievalMethod + def add_RetrievalMethod(self, value): self.RetrievalMethod.append(value) + def insert_RetrievalMethod_at(self, index, value): self.RetrievalMethod.insert(index, value) + def replace_RetrievalMethod_at(self, index, value): self.RetrievalMethod[index] = value + def get_X509Data(self): return self.X509Data + def set_X509Data(self, X509Data): self.X509Data = X509Data + def add_X509Data(self, value): self.X509Data.append(value) + def insert_X509Data_at(self, index, value): self.X509Data.insert(index, value) + def replace_X509Data_at(self, index, value): self.X509Data[index] = value + def get_PGPData(self): return self.PGPData + def set_PGPData(self, PGPData): self.PGPData = PGPData + def add_PGPData(self, value): self.PGPData.append(value) + def insert_PGPData_at(self, index, value): self.PGPData.insert(index, value) + def replace_PGPData_at(self, index, value): self.PGPData[index] = value + def get_SPKIData(self): return self.SPKIData + def set_SPKIData(self, SPKIData): self.SPKIData = SPKIData + def add_SPKIData(self, value): self.SPKIData.append(value) + def insert_SPKIData_at(self, index, value): self.SPKIData.insert(index, value) + def replace_SPKIData_at(self, index, value): self.SPKIData[index] = value + def get_MgmtData(self): return self.MgmtData + def set_MgmtData(self, MgmtData): self.MgmtData = MgmtData + def add_MgmtData(self, value): self.MgmtData.append(value) + def insert_MgmtData_at(self, index, value): self.MgmtData.insert(index, value) + def replace_MgmtData_at(self, index, value): self.MgmtData[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.KeyName or + self.KeyValue or + self.RetrievalMethod or + self.X509Data or + self.PGPData or + self.SPKIData or + self.MgmtData or + self.anytypeobjs_ is not None or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='KeyInfoType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('KeyInfoType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='KeyInfoType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='KeyInfoType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='KeyInfoType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='KeyInfoType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for KeyName_ in self.KeyName: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(KeyName_), input_name='KeyName')), eol_)) + for KeyValue_ in self.KeyValue: + KeyValue_.export(outfile, level, namespace_='ds:', name_='KeyValue', pretty_print=pretty_print) + for RetrievalMethod_ in self.RetrievalMethod: + RetrievalMethod_.export(outfile, level, namespace_='ds:', name_='RetrievalMethod', pretty_print=pretty_print) + for X509Data_ in self.X509Data: + X509Data_.export(outfile, level, namespace_='ds:', name_='X509Data', pretty_print=pretty_print) + for PGPData_ in self.PGPData: + PGPData_.export(outfile, level, namespace_='ds:', name_='PGPData', pretty_print=pretty_print) + for SPKIData_ in self.SPKIData: + SPKIData_.export(outfile, level, namespace_='ds:', name_='SPKIData', pretty_print=pretty_print) + for MgmtData_ in self.MgmtData: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(MgmtData_), input_name='MgmtData')), eol_)) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'KeyName' and child_.text is not None: + valuestr_ = child_.text + obj_ = self.mixedclass_(MixedContainer.CategorySimple, + MixedContainer.TypeString, 'KeyName', valuestr_) + self.content_.append(obj_) + elif nodeName_ == 'KeyValue': + obj_ = KeyValueType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'KeyValue', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_KeyValue'): + self.add_KeyValue(obj_.value) + elif hasattr(self, 'set_KeyValue'): + self.set_KeyValue(obj_.value) + elif nodeName_ == 'RetrievalMethod': + obj_ = RetrievalMethodType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'RetrievalMethod', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_RetrievalMethod'): + self.add_RetrievalMethod(obj_.value) + elif hasattr(self, 'set_RetrievalMethod'): + self.set_RetrievalMethod(obj_.value) + elif nodeName_ == 'X509Data': + obj_ = X509DataType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'X509Data', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_X509Data'): + self.add_X509Data(obj_.value) + elif hasattr(self, 'set_X509Data'): + self.set_X509Data(obj_.value) + elif nodeName_ == 'PGPData': + obj_ = PGPDataType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'PGPData', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_PGPData'): + self.add_PGPData(obj_.value) + elif hasattr(self, 'set_PGPData'): + self.set_PGPData(obj_.value) + elif nodeName_ == 'SPKIData': + obj_ = SPKIDataType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'SPKIData', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_SPKIData'): + self.add_SPKIData(obj_.value) + elif hasattr(self, 'set_SPKIData'): + self.set_SPKIData(obj_.value) + elif nodeName_ == 'MgmtData' and child_.text is not None: + valuestr_ = child_.text + obj_ = self.mixedclass_(MixedContainer.CategorySimple, + MixedContainer.TypeString, 'MgmtData', valuestr_) + self.content_.append(obj_) + elif nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class KeyInfoType + + +class KeyValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, DSAKeyValue=None, RSAKeyValue=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.DSAKeyValue = DSAKeyValue + self.RSAKeyValue = RSAKeyValue + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, KeyValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if KeyValueType.subclass: + return KeyValueType.subclass(*args_, **kwargs_) + else: + return KeyValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_DSAKeyValue(self): return self.DSAKeyValue + def set_DSAKeyValue(self, DSAKeyValue): self.DSAKeyValue = DSAKeyValue + def get_RSAKeyValue(self): return self.RSAKeyValue + def set_RSAKeyValue(self, RSAKeyValue): self.RSAKeyValue = RSAKeyValue + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.DSAKeyValue is not None or + self.RSAKeyValue is not None or + self.anytypeobjs_ is not None or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='KeyValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('KeyValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='KeyValueType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='KeyValueType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='KeyValueType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='KeyValueType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.DSAKeyValue is not None: + self.DSAKeyValue.export(outfile, level, namespace_='ds:', name_='DSAKeyValue', pretty_print=pretty_print) + if self.RSAKeyValue is not None: + self.RSAKeyValue.export(outfile, level, namespace_='ds:', name_='RSAKeyValue', pretty_print=pretty_print) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'DSAKeyValue': + obj_ = DSAKeyValueType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'DSAKeyValue', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_DSAKeyValue'): + self.add_DSAKeyValue(obj_.value) + elif hasattr(self, 'set_DSAKeyValue'): + self.set_DSAKeyValue(obj_.value) + elif nodeName_ == 'RSAKeyValue': + obj_ = RSAKeyValueType.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, 'RSAKeyValue', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_RSAKeyValue'): + self.add_RSAKeyValue(obj_.value) + elif hasattr(self, 'set_RSAKeyValue'): + self.set_RSAKeyValue(obj_.value) + elif nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class KeyValueType + + +class RetrievalMethodType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, URI=None, Type=None, Transforms=None): + self.original_tagname_ = None + self.URI = _cast(None, URI) + self.Type = _cast(None, Type) + self.Transforms = Transforms + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, RetrievalMethodType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if RetrievalMethodType.subclass: + return RetrievalMethodType.subclass(*args_, **kwargs_) + else: + return RetrievalMethodType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Transforms(self): return self.Transforms + def set_Transforms(self, Transforms): self.Transforms = Transforms + def get_URI(self): return self.URI + def set_URI(self, URI): self.URI = URI + def get_Type(self): return self.Type + def set_Type(self, Type): self.Type = Type + def hasContent_(self): + if ( + self.Transforms is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='RetrievalMethodType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('RetrievalMethodType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='RetrievalMethodType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='RetrievalMethodType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='RetrievalMethodType'): + if self.URI is not None and 'URI' not in already_processed: + already_processed.add('URI') + outfile.write(' URI=%s' % (quote_attrib(self.URI), )) + if self.Type is not None and 'Type' not in already_processed: + already_processed.add('Type') + outfile.write(' Type=%s' % (quote_attrib(self.Type), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='RetrievalMethodType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Transforms is not None: + self.Transforms.export(outfile, level, namespace_='ds:', name_='Transforms', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('URI', node) + if value is not None and 'URI' not in already_processed: + already_processed.add('URI') + self.URI = value + value = find_attr_value_('Type', node) + if value is not None and 'Type' not in already_processed: + already_processed.add('Type') + self.Type = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Transforms': + obj_ = TransformsType.factory() + obj_.build(child_) + self.Transforms = obj_ + obj_.original_tagname_ = 'Transforms' +# end class RetrievalMethodType + + +class X509DataType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, X509IssuerSerial=None, X509SKI=None, X509SubjectName=None, X509Certificate=None, X509CRL=None, anytypeobjs_=None): + self.original_tagname_ = None + if X509IssuerSerial is None: + self.X509IssuerSerial = [] + else: + self.X509IssuerSerial = X509IssuerSerial + if X509SKI is None: + self.X509SKI = [] + else: + self.X509SKI = X509SKI + if X509SubjectName is None: + self.X509SubjectName = [] + else: + self.X509SubjectName = X509SubjectName + if X509Certificate is None: + self.X509Certificate = [] + else: + self.X509Certificate = X509Certificate + if X509CRL is None: + self.X509CRL = [] + else: + self.X509CRL = X509CRL + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, X509DataType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if X509DataType.subclass: + return X509DataType.subclass(*args_, **kwargs_) + else: + return X509DataType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_X509IssuerSerial(self): return self.X509IssuerSerial + def set_X509IssuerSerial(self, X509IssuerSerial): self.X509IssuerSerial = X509IssuerSerial + def add_X509IssuerSerial(self, value): self.X509IssuerSerial.append(value) + def insert_X509IssuerSerial_at(self, index, value): self.X509IssuerSerial.insert(index, value) + def replace_X509IssuerSerial_at(self, index, value): self.X509IssuerSerial[index] = value + def get_X509SKI(self): return self.X509SKI + def set_X509SKI(self, X509SKI): self.X509SKI = X509SKI + def add_X509SKI(self, value): self.X509SKI.append(value) + def insert_X509SKI_at(self, index, value): self.X509SKI.insert(index, value) + def replace_X509SKI_at(self, index, value): self.X509SKI[index] = value + def get_X509SubjectName(self): return self.X509SubjectName + def set_X509SubjectName(self, X509SubjectName): self.X509SubjectName = X509SubjectName + def add_X509SubjectName(self, value): self.X509SubjectName.append(value) + def insert_X509SubjectName_at(self, index, value): self.X509SubjectName.insert(index, value) + def replace_X509SubjectName_at(self, index, value): self.X509SubjectName[index] = value + def get_X509Certificate(self): return self.X509Certificate + def set_X509Certificate(self, X509Certificate): self.X509Certificate = X509Certificate + def add_X509Certificate(self, value): self.X509Certificate.append(value) + def insert_X509Certificate_at(self, index, value): self.X509Certificate.insert(index, value) + def replace_X509Certificate_at(self, index, value): self.X509Certificate[index] = value + def get_X509CRL(self): return self.X509CRL + def set_X509CRL(self, X509CRL): self.X509CRL = X509CRL + def add_X509CRL(self, value): self.X509CRL.append(value) + def insert_X509CRL_at(self, index, value): self.X509CRL.insert(index, value) + def replace_X509CRL_at(self, index, value): self.X509CRL[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def hasContent_(self): + if ( + self.X509IssuerSerial or + self.X509SKI or + self.X509SubjectName or + self.X509Certificate or + self.X509CRL or + self.anytypeobjs_ is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='X509DataType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('X509DataType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='X509DataType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='X509DataType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='X509DataType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='X509DataType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for X509IssuerSerial_ in self.X509IssuerSerial: + X509IssuerSerial_.export(outfile, level, namespace_, name_='X509IssuerSerial', pretty_print=pretty_print) + for X509SKI_ in self.X509SKI: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(X509SKI_), input_name='X509SKI')), eol_)) + for X509SubjectName_ in self.X509SubjectName: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(X509SubjectName_), input_name='X509SubjectName')), eol_)) + for X509Certificate_ in self.X509Certificate: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(X509Certificate_), input_name='X509Certificate')), eol_)) + for X509CRL_ in self.X509CRL: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(X509CRL_), input_name='X509CRL')), eol_)) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'X509IssuerSerial': + obj_ = X509IssuerSerialType.factory() + obj_.build(child_) + self.X509IssuerSerial.append(obj_) + obj_.original_tagname_ = 'X509IssuerSerial' + elif nodeName_ == 'X509SKI': + X509SKI_ = child_.text + X509SKI_ = self.gds_validate_string(X509SKI_, node, 'X509SKI') + self.X509SKI.append(X509SKI_) + elif nodeName_ == 'X509SubjectName': + X509SubjectName_ = child_.text + X509SubjectName_ = self.gds_validate_string(X509SubjectName_, node, 'X509SubjectName') + self.X509SubjectName.append(X509SubjectName_) + elif nodeName_ == 'X509Certificate': + X509Certificate_ = child_.text + X509Certificate_ = self.gds_validate_string(X509Certificate_, node, 'X509Certificate') + self.X509Certificate.append(X509Certificate_) + elif nodeName_ == 'X509CRL': + X509CRL_ = child_.text + X509CRL_ = self.gds_validate_string(X509CRL_, node, 'X509CRL') + self.X509CRL.append(X509CRL_) + else: + obj_ = self.gds_build_any(child_, 'X509DataType') + if obj_ is not None: + self.set_anytypeobjs_(obj_) +# end class X509DataType + + +class X509IssuerSerialType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, X509IssuerName=None, X509SerialNumber=None): + self.original_tagname_ = None + self.X509IssuerName = X509IssuerName + self.X509SerialNumber = X509SerialNumber + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, X509IssuerSerialType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if X509IssuerSerialType.subclass: + return X509IssuerSerialType.subclass(*args_, **kwargs_) + else: + return X509IssuerSerialType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_X509IssuerName(self): return self.X509IssuerName + def set_X509IssuerName(self, X509IssuerName): self.X509IssuerName = X509IssuerName + def get_X509SerialNumber(self): return self.X509SerialNumber + def set_X509SerialNumber(self, X509SerialNumber): self.X509SerialNumber = X509SerialNumber + def hasContent_(self): + if ( + self.X509IssuerName is not None or + self.X509SerialNumber is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='X509IssuerSerialType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('X509IssuerSerialType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='X509IssuerSerialType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='X509IssuerSerialType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='X509IssuerSerialType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='X509IssuerSerialType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.X509IssuerName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.X509IssuerName), input_name='X509IssuerName')), eol_)) + if self.X509SerialNumber is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.X509SerialNumber), input_name='X509SerialNumber')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'X509IssuerName': + X509IssuerName_ = child_.text + X509IssuerName_ = self.gds_validate_string(X509IssuerName_, node, 'X509IssuerName') + self.X509IssuerName = X509IssuerName_ + elif nodeName_ == 'X509SerialNumber': + X509SerialNumber_ = child_.text + X509SerialNumber_ = self.gds_validate_string(X509SerialNumber_, node, 'X509SerialNumber') + self.X509SerialNumber = X509SerialNumber_ +# end class X509IssuerSerialType + + +class PGPDataType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, PGPKeyID=None, PGPKeyPacket=None, anytypeobjs_=None): + self.original_tagname_ = None + self.PGPKeyID = PGPKeyID + self.PGPKeyPacket = PGPKeyPacket + if anytypeobjs_ is None: + self.anytypeobjs_ = [] + else: + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PGPDataType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PGPDataType.subclass: + return PGPDataType.subclass(*args_, **kwargs_) + else: + return PGPDataType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_PGPKeyID(self): return self.PGPKeyID + def set_PGPKeyID(self, PGPKeyID): self.PGPKeyID = PGPKeyID + def get_PGPKeyPacket(self): return self.PGPKeyPacket + def set_PGPKeyPacket(self, PGPKeyPacket): self.PGPKeyPacket = PGPKeyPacket + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def add_anytypeobjs_(self, value): self.anytypeobjs_.append(value) + def insert_anytypeobjs_(self, index, value): self._anytypeobjs_[index] = value + def hasContent_(self): + if ( + self.PGPKeyID is not None or + self.PGPKeyPacket is not None or + self.anytypeobjs_ + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='PGPDataType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PGPDataType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PGPDataType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='PGPDataType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='PGPDataType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='PGPDataType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.PGPKeyID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.PGPKeyID), input_name='PGPKeyID')), eol_)) + if self.PGPKeyPacket is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.PGPKeyPacket), input_name='PGPKeyPacket')), eol_)) + for obj_ in self.anytypeobjs_: + obj_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'PGPKeyID': + PGPKeyID_ = child_.text + PGPKeyID_ = self.gds_validate_string(PGPKeyID_, node, 'PGPKeyID') + self.PGPKeyID = PGPKeyID_ + elif nodeName_ == 'PGPKeyPacket': + PGPKeyPacket_ = child_.text + PGPKeyPacket_ = self.gds_validate_string(PGPKeyPacket_, node, 'PGPKeyPacket') + self.PGPKeyPacket = PGPKeyPacket_ + else: + obj_ = self.gds_build_any(child_, 'PGPDataType') + if obj_ is not None: + self.add_anytypeobjs_(obj_) +# end class PGPDataType + + +class SPKIDataType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, SPKISexp=None, anytypeobjs_=None): + self.original_tagname_ = None + if SPKISexp is None: + self.SPKISexp = [] + else: + self.SPKISexp = SPKISexp + self.anytypeobjs_ = anytypeobjs_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SPKIDataType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SPKIDataType.subclass: + return SPKIDataType.subclass(*args_, **kwargs_) + else: + return SPKIDataType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_SPKISexp(self): return self.SPKISexp + def set_SPKISexp(self, SPKISexp): self.SPKISexp = SPKISexp + def add_SPKISexp(self, value): self.SPKISexp.append(value) + def insert_SPKISexp_at(self, index, value): self.SPKISexp.insert(index, value) + def replace_SPKISexp_at(self, index, value): self.SPKISexp[index] = value + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def hasContent_(self): + if ( + self.SPKISexp or + self.anytypeobjs_ is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SPKIDataType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SPKIDataType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SPKIDataType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SPKIDataType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SPKIDataType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='SPKIDataType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for SPKISexp_ in self.SPKISexp: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(SPKISexp_), input_name='SPKISexp')), eol_)) + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'SPKISexp': + SPKISexp_ = child_.text + SPKISexp_ = self.gds_validate_string(SPKISexp_, node, 'SPKISexp') + self.SPKISexp.append(SPKISexp_) + else: + obj_ = self.gds_build_any(child_, 'SPKIDataType') + if obj_ is not None: + self.set_anytypeobjs_(obj_) +# end class SPKIDataType + + +class ObjectType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, MimeType=None, Encoding=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + self.MimeType = _cast(None, MimeType) + self.Encoding = _cast(None, Encoding) + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ObjectType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ObjectType.subclass: + return ObjectType.subclass(*args_, **kwargs_) + else: + return ObjectType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_MimeType(self): return self.MimeType + def set_MimeType(self, MimeType): self.MimeType = MimeType + def get_Encoding(self): return self.Encoding + def set_Encoding(self, Encoding): self.Encoding = Encoding + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ is not None or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='ObjectType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ObjectType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ObjectType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='ObjectType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='ObjectType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + if self.MimeType is not None and 'MimeType' not in already_processed: + already_processed.add('MimeType') + outfile.write(' MimeType=%s' % (quote_attrib(self.MimeType), )) + if self.Encoding is not None and 'Encoding' not in already_processed: + already_processed.add('Encoding') + outfile.write(' Encoding=%s' % (quote_attrib(self.Encoding), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='ObjectType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + value = find_attr_value_('MimeType', node) + if value is not None and 'MimeType' not in already_processed: + already_processed.add('MimeType') + self.MimeType = value + value = find_attr_value_('Encoding', node) + if value is not None and 'Encoding' not in already_processed: + already_processed.add('Encoding') + self.Encoding = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class ObjectType + + +class ManifestType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, Reference=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + if Reference is None: + self.Reference = [] + else: + self.Reference = Reference + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ManifestType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ManifestType.subclass: + return ManifestType.subclass(*args_, **kwargs_) + else: + return ManifestType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Reference(self): return self.Reference + def set_Reference(self, Reference): self.Reference = Reference + def add_Reference(self, value): self.Reference.append(value) + def insert_Reference_at(self, index, value): self.Reference.insert(index, value) + def replace_Reference_at(self, index, value): self.Reference[index] = value + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + self.Reference + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='ManifestType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ManifestType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ManifestType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='ManifestType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='ManifestType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='ManifestType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for Reference_ in self.Reference: + Reference_.export(outfile, level, namespace_='ds:', name_='Reference', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Reference': + obj_ = ReferenceType.factory() + obj_.build(child_) + self.Reference.append(obj_) + obj_.original_tagname_ = 'Reference' +# end class ManifestType + + +class SignaturePropertiesType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Id=None, SignatureProperty=None): + self.original_tagname_ = None + self.Id = _cast(None, Id) + if SignatureProperty is None: + self.SignatureProperty = [] + else: + self.SignatureProperty = SignatureProperty + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignaturePropertiesType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignaturePropertiesType.subclass: + return SignaturePropertiesType.subclass(*args_, **kwargs_) + else: + return SignaturePropertiesType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_SignatureProperty(self): return self.SignatureProperty + def set_SignatureProperty(self, SignatureProperty): self.SignatureProperty = SignatureProperty + def add_SignatureProperty(self, value): self.SignatureProperty.append(value) + def insert_SignatureProperty_at(self, index, value): self.SignatureProperty.insert(index, value) + def replace_SignatureProperty_at(self, index, value): self.SignatureProperty[index] = value + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def hasContent_(self): + if ( + self.SignatureProperty + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignaturePropertiesType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignaturePropertiesType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignaturePropertiesType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignaturePropertiesType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignaturePropertiesType'): + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignaturePropertiesType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for SignatureProperty_ in self.SignatureProperty: + SignatureProperty_.export(outfile, level, namespace_='ds:', name_='SignatureProperty', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'SignatureProperty': + obj_ = SignaturePropertyType.factory() + obj_.build(child_) + self.SignatureProperty.append(obj_) + obj_.original_tagname_ = 'SignatureProperty' +# end class SignaturePropertiesType + + +class SignaturePropertyType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Target=None, Id=None, anytypeobjs_=None, valueOf_=None, mixedclass_=None, content_=None): + self.original_tagname_ = None + self.Target = _cast(None, Target) + self.Id = _cast(None, Id) + self.anytypeobjs_ = anytypeobjs_ + self.valueOf_ = valueOf_ + if mixedclass_ is None: + self.mixedclass_ = MixedContainer + else: + self.mixedclass_ = mixedclass_ + if content_ is None: + self.content_ = [] + else: + self.content_ = content_ + self.valueOf_ = valueOf_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SignaturePropertyType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SignaturePropertyType.subclass: + return SignaturePropertyType.subclass(*args_, **kwargs_) + else: + return SignaturePropertyType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_anytypeobjs_(self): return self.anytypeobjs_ + def set_anytypeobjs_(self, anytypeobjs_): self.anytypeobjs_ = anytypeobjs_ + def get_Target(self): return self.Target + def set_Target(self, Target): self.Target = Target + def get_Id(self): return self.Id + def set_Id(self, Id): self.Id = Id + def get_valueOf_(self): return self.valueOf_ + def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_ + def hasContent_(self): + if ( + self.anytypeobjs_ is not None or + (1 if type(self.valueOf_) in [int,float] else self.valueOf_) + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='SignaturePropertyType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SignaturePropertyType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SignaturePropertyType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='SignaturePropertyType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='SignaturePropertyType'): + if self.Target is not None and 'Target' not in already_processed: + already_processed.add('Target') + outfile.write(' Target=%s' % (quote_attrib(self.Target), )) + if self.Id is not None and 'Id' not in already_processed: + already_processed.add('Id') + outfile.write(' Id=%s' % (quote_attrib(self.Id), )) + def exportChildren(self, outfile, level, namespace_='ds:', name_='SignaturePropertyType', fromsubclass_=False, pretty_print=True): + if not fromsubclass_: + for item_ in self.content_: + item_.export(outfile, level, item_.name, namespace_, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.anytypeobjs_ is not None: + self.anytypeobjs_.export(outfile, level, namespace_, pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + self.valueOf_ = get_all_text_(node) + if node.text is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', node.text) + self.content_.append(obj_) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('Target', node) + if value is not None and 'Target' not in already_processed: + already_processed.add('Target') + self.Target = value + value = find_attr_value_('Id', node) + if value is not None and 'Id' not in already_processed: + already_processed.add('Id') + self.Id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == '': + obj_ = __ANY__.factory() + obj_.build(child_) + obj_ = self.mixedclass_(MixedContainer.CategoryComplex, + MixedContainer.TypeNone, '', obj_) + self.content_.append(obj_) + if hasattr(self, 'add_'): + self.add_(obj_.value) + elif hasattr(self, 'set_'): + self.set_(obj_.value) + if not fromsubclass_ and child_.tail is not None: + obj_ = self.mixedclass_(MixedContainer.CategoryText, + MixedContainer.TypeNone, '', child_.tail) + self.content_.append(obj_) +# end class SignaturePropertyType + + +class DSAKeyValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, P=None, Q=None, G=None, Y=None, J=None, Seed=None, PgenCounter=None): + self.original_tagname_ = None + self.P = P + self.validate_CryptoBinary(self.P) + self.Q = Q + self.validate_CryptoBinary(self.Q) + self.G = G + self.validate_CryptoBinary(self.G) + self.Y = Y + self.validate_CryptoBinary(self.Y) + self.J = J + self.validate_CryptoBinary(self.J) + self.Seed = Seed + self.validate_CryptoBinary(self.Seed) + self.PgenCounter = PgenCounter + self.validate_CryptoBinary(self.PgenCounter) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, DSAKeyValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if DSAKeyValueType.subclass: + return DSAKeyValueType.subclass(*args_, **kwargs_) + else: + return DSAKeyValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_P(self): return self.P + def set_P(self, P): self.P = P + def get_Q(self): return self.Q + def set_Q(self, Q): self.Q = Q + def get_G(self): return self.G + def set_G(self, G): self.G = G + def get_Y(self): return self.Y + def set_Y(self, Y): self.Y = Y + def get_J(self): return self.J + def set_J(self, J): self.J = J + def get_Seed(self): return self.Seed + def set_Seed(self, Seed): self.Seed = Seed + def get_PgenCounter(self): return self.PgenCounter + def set_PgenCounter(self, PgenCounter): self.PgenCounter = PgenCounter + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.P is not None or + self.Q is not None or + self.G is not None or + self.Y is not None or + self.J is not None or + self.Seed is not None or + self.PgenCounter is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='DSAKeyValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('DSAKeyValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='DSAKeyValueType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='DSAKeyValueType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='DSAKeyValueType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='DSAKeyValueType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.P is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.P, input_name='P'), eol_)) + if self.Q is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Q, input_name='Q'), eol_)) + if self.G is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.G, input_name='G'), eol_)) + if self.Y is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Y, input_name='Y'), eol_)) + if self.J is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.J, input_name='J'), eol_)) + if self.Seed is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Seed, input_name='Seed'), eol_)) + if self.PgenCounter is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.PgenCounter, input_name='PgenCounter'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'P': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'P') + else: + bval_ = None + self.P = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.P) + elif nodeName_ == 'Q': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Q') + else: + bval_ = None + self.Q = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Q) + elif nodeName_ == 'G': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'G') + else: + bval_ = None + self.G = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.G) + elif nodeName_ == 'Y': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Y') + else: + bval_ = None + self.Y = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Y) + elif nodeName_ == 'J': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'J') + else: + bval_ = None + self.J = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.J) + elif nodeName_ == 'Seed': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Seed') + else: + bval_ = None + self.Seed = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Seed) + elif nodeName_ == 'PgenCounter': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'PgenCounter') + else: + bval_ = None + self.PgenCounter = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.PgenCounter) +# end class DSAKeyValueType + + +class RSAKeyValueType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Modulus=None, Exponent=None): + self.original_tagname_ = None + self.Modulus = Modulus + self.validate_CryptoBinary(self.Modulus) + self.Exponent = Exponent + self.validate_CryptoBinary(self.Exponent) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, RSAKeyValueType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if RSAKeyValueType.subclass: + return RSAKeyValueType.subclass(*args_, **kwargs_) + else: + return RSAKeyValueType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Modulus(self): return self.Modulus + def set_Modulus(self, Modulus): self.Modulus = Modulus + def get_Exponent(self): return self.Exponent + def set_Exponent(self, Exponent): self.Exponent = Exponent + def validate_CryptoBinary(self, value): + # Validate type CryptoBinary, a restriction on base64Binary. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.Modulus is not None or + self.Exponent is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='ds:', name_='RSAKeyValueType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('RSAKeyValueType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='RSAKeyValueType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='ds:', name_='RSAKeyValueType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='ds:', name_='RSAKeyValueType'): + pass + def exportChildren(self, outfile, level, namespace_='ds:', name_='RSAKeyValueType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Modulus is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Modulus, input_name='Modulus'), eol_)) + if self.Exponent is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_base64(self.Exponent, input_name='Exponent'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Modulus': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Modulus') + else: + bval_ = None + self.Modulus = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Modulus) + elif nodeName_ == 'Exponent': + sval_ = child_.text + if sval_ is not None: + try: + bval_ = base64.b64decode(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires base64 encoded string: %s' % exp) + bval_ = self.gds_validate_base64(bval_, node, 'Exponent') + else: + bval_ = None + self.Exponent = bval_ + # validate type CryptoBinary + self.validate_CryptoBinary(self.Exponent) +# end class RSAKeyValueType + + +class oadrEventType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, eiEvent=None, oadrResponseRequired=None): + self.original_tagname_ = None + self.eiEvent = eiEvent + self.oadrResponseRequired = oadrResponseRequired + self.validate_ResponseRequiredType(self.oadrResponseRequired) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrEventType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrEventType.subclass: + return oadrEventType.subclass(*args_, **kwargs_) + else: + return oadrEventType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiEvent(self): return self.eiEvent + def set_eiEvent(self, eiEvent): self.eiEvent = eiEvent + def get_oadrResponseRequired(self): return self.oadrResponseRequired + def set_oadrResponseRequired(self, oadrResponseRequired): self.oadrResponseRequired = oadrResponseRequired + def validate_ResponseRequiredType(self, value): + # Validate type ResponseRequiredType, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['always', 'never'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on ResponseRequiredType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.eiEvent is not None or + self.oadrResponseRequired is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrEventType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrEventType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrEventType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrEventType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrEventType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrEventType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiEvent is not None: + self.eiEvent.export(outfile, level, namespace_='ei:', name_='eiEvent', pretty_print=pretty_print) + if self.oadrResponseRequired is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrResponseRequired), input_name='oadrResponseRequired')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiEvent': + obj_ = eiEventType.factory() + obj_.build(child_) + self.eiEvent = obj_ + obj_.original_tagname_ = 'eiEvent' + elif nodeName_ == 'oadrResponseRequired': + oadrResponseRequired_ = child_.text + oadrResponseRequired_ = self.gds_validate_string(oadrResponseRequired_, node, 'oadrResponseRequired') + self.oadrResponseRequired = oadrResponseRequired_ + # validate type ResponseRequiredType + self.validate_ResponseRequiredType(self.oadrResponseRequired) +# end class oadrEventType + + +class oadrExtensionsType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrExtension=None): + self.original_tagname_ = None + if oadrExtension is None: + self.oadrExtension = [] + else: + self.oadrExtension = oadrExtension + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrExtensionsType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrExtensionsType.subclass: + return oadrExtensionsType.subclass(*args_, **kwargs_) + else: + return oadrExtensionsType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrExtension(self): return self.oadrExtension + def set_oadrExtension(self, oadrExtension): self.oadrExtension = oadrExtension + def add_oadrExtension(self, value): self.oadrExtension.append(value) + def insert_oadrExtension_at(self, index, value): self.oadrExtension.insert(index, value) + def replace_oadrExtension_at(self, index, value): self.oadrExtension[index] = value + def hasContent_(self): + if ( + self.oadrExtension + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrExtensionsType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrExtensionsType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrExtensionsType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrExtensionsType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrExtensionsType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrExtensionsType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + for oadrExtension_ in self.oadrExtension: + oadrExtension_.export(outfile, level, namespace_, name_='oadrExtension', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrExtension': + obj_ = oadrExtensionType.factory() + obj_.build(child_) + self.oadrExtension.append(obj_) + obj_.original_tagname_ = 'oadrExtension' +# end class oadrExtensionsType + + +class oadrExtensionType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrExtensionName=None, oadrInfo=None): + self.original_tagname_ = None + self.oadrExtensionName = oadrExtensionName + if oadrInfo is None: + self.oadrInfo = [] + else: + self.oadrInfo = oadrInfo + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrExtensionType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrExtensionType.subclass: + return oadrExtensionType.subclass(*args_, **kwargs_) + else: + return oadrExtensionType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrExtensionName(self): return self.oadrExtensionName + def set_oadrExtensionName(self, oadrExtensionName): self.oadrExtensionName = oadrExtensionName + def get_oadrInfo(self): return self.oadrInfo + def set_oadrInfo(self, oadrInfo): self.oadrInfo = oadrInfo + def add_oadrInfo(self, value): self.oadrInfo.append(value) + def insert_oadrInfo_at(self, index, value): self.oadrInfo.insert(index, value) + def replace_oadrInfo_at(self, index, value): self.oadrInfo[index] = value + def hasContent_(self): + if ( + self.oadrExtensionName is not None or + self.oadrInfo + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrExtensionType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrExtensionType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrExtensionType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrExtensionType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrExtensionType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrExtensionType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrExtensionName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrExtensionName), input_name='oadrExtensionName')), eol_)) + for oadrInfo_ in self.oadrInfo: + oadrInfo_.export(outfile, level, namespace_='oadr:', name_='oadrInfo', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrExtensionName': + oadrExtensionName_ = child_.text + oadrExtensionName_ = self.gds_validate_string(oadrExtensionName_, node, 'oadrExtensionName') + self.oadrExtensionName = oadrExtensionName_ + elif nodeName_ == 'oadrInfo': + obj_ = oadrInfo.factory() + obj_.build(child_) + self.oadrInfo.append(obj_) + obj_.original_tagname_ = 'oadrInfo' +# end class oadrExtensionType + + +class oadrProfileType1(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrProfileName=None, oadrTransports=None): + self.original_tagname_ = None + self.oadrProfileName = oadrProfileName + self.validate_oadrProfileType(self.oadrProfileName) + self.oadrTransports = oadrTransports + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrProfileType1) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrProfileType1.subclass: + return oadrProfileType1.subclass(*args_, **kwargs_) + else: + return oadrProfileType1(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrProfileName(self): return self.oadrProfileName + def set_oadrProfileName(self, oadrProfileName): self.oadrProfileName = oadrProfileName + def get_oadrTransports(self): return self.oadrTransports + def set_oadrTransports(self, oadrTransports): self.oadrTransports = oadrTransports + def validate_oadrProfileType(self, value): + # Validate type oadrProfileType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['2.0a', '2.0b'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on oadrProfileType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.oadrProfileName is not None or + self.oadrTransports is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrProfileType1', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrProfileType1') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrProfileType1') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrProfileType1', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrProfileType1'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrProfileType1', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrProfileName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrProfileName), input_name='oadrProfileName')), eol_)) + if self.oadrTransports is not None: + self.oadrTransports.export(outfile, level, namespace_='oadr:', name_='oadrTransports', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrProfileName': + oadrProfileName_ = child_.text + if oadrProfileName_: + oadrProfileName_ = re_.sub(String_cleanup_pat_, " ", oadrProfileName_).strip() + else: + oadrProfileName_ = "" + oadrProfileName_ = self.gds_validate_string(oadrProfileName_, node, 'oadrProfileName') + self.oadrProfileName = oadrProfileName_ + # validate type oadrProfileType + self.validate_oadrProfileType(self.oadrProfileName) + elif nodeName_ == 'oadrTransports': + obj_ = oadrTransports.factory() + obj_.build(child_) + self.oadrTransports = obj_ + obj_.original_tagname_ = 'oadrTransports' +# end class oadrProfileType1 + + +class oadrTransportType2(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrTransportName=None): + self.original_tagname_ = None + self.oadrTransportName = oadrTransportName + self.validate_oadrTransportType(self.oadrTransportName) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrTransportType2) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrTransportType2.subclass: + return oadrTransportType2.subclass(*args_, **kwargs_) + else: + return oadrTransportType2(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrTransportName(self): return self.oadrTransportName + def set_oadrTransportName(self, oadrTransportName): self.oadrTransportName = oadrTransportName + def validate_oadrTransportType(self, value): + # Validate type oadrTransportType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['simpleHttp', 'xmpp'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on oadrTransportType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.oadrTransportName is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrTransportType2', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrTransportType2') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrTransportType2') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrTransportType2', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrTransportType2'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrTransportType2', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrTransportName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrTransportName), input_name='oadrTransportName')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrTransportName': + oadrTransportName_ = child_.text + if oadrTransportName_: + oadrTransportName_ = re_.sub(String_cleanup_pat_, " ", oadrTransportName_).strip() + else: + oadrTransportName_ = "" + oadrTransportName_ = self.gds_validate_string(oadrTransportName_, node, 'oadrTransportName') + self.oadrTransportName = oadrTransportName_ + # validate type oadrTransportType + self.validate_oadrTransportType(self.oadrTransportName) +# end class oadrTransportType2 + + +class oadrServiceType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, oadrServiceName=None, oadrInfo=None): + self.original_tagname_ = None + self.oadrServiceName = oadrServiceName + self.validate_oadrServiceNameType(self.oadrServiceName) + if oadrInfo is None: + self.oadrInfo = [] + else: + self.oadrInfo = oadrInfo + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrServiceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrServiceType.subclass: + return oadrServiceType.subclass(*args_, **kwargs_) + else: + return oadrServiceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrServiceName(self): return self.oadrServiceName + def set_oadrServiceName(self, oadrServiceName): self.oadrServiceName = oadrServiceName + def get_oadrInfo(self): return self.oadrInfo + def set_oadrInfo(self, oadrInfo): self.oadrInfo = oadrInfo + def add_oadrInfo(self, value): self.oadrInfo.append(value) + def insert_oadrInfo_at(self, index, value): self.oadrInfo.insert(index, value) + def replace_oadrInfo_at(self, index, value): self.oadrInfo[index] = value + def validate_oadrServiceNameType(self, value): + # Validate type oadrServiceNameType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['EiEvent', 'EiOpt', 'EiReport', 'EiRegisterParty', 'OadrPoll'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on oadrServiceNameType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.oadrServiceName is not None or + self.oadrInfo + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrServiceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrServiceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrServiceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrServiceType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrServiceType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrServiceType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrServiceName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrServiceName), input_name='oadrServiceName')), eol_)) + for oadrInfo_ in self.oadrInfo: + oadrInfo_.export(outfile, level, namespace_='oadr:', name_='oadrInfo', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrServiceName': + oadrServiceName_ = child_.text + if oadrServiceName_: + oadrServiceName_ = re_.sub(String_cleanup_pat_, " ", oadrServiceName_).strip() + else: + oadrServiceName_ = "" + oadrServiceName_ = self.gds_validate_string(oadrServiceName_, node, 'oadrServiceName') + self.oadrServiceName = oadrServiceName_ + # validate type oadrServiceNameType + self.validate_oadrServiceNameType(self.oadrServiceName) + elif nodeName_ == 'oadrInfo': + obj_ = oadrInfo.factory() + obj_.build(child_) + self.oadrInfo.append(obj_) + obj_.original_tagname_ = 'oadrInfo' +# end class oadrServiceType + + +class toleranceType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, tolerate=None): + self.original_tagname_ = None + self.tolerate = tolerate + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, toleranceType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if toleranceType.subclass: + return toleranceType.subclass(*args_, **kwargs_) + else: + return toleranceType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_tolerate(self): return self.tolerate + def set_tolerate(self, tolerate): self.tolerate = tolerate + def hasContent_(self): + if ( + self.tolerate is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='toleranceType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('toleranceType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='toleranceType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='toleranceType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='toleranceType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='toleranceType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.tolerate is not None: + self.tolerate.export(outfile, level, namespace_, name_='tolerate', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'tolerate': + obj_ = tolerateType.factory() + obj_.build(child_) + self.tolerate = obj_ + obj_.original_tagname_ = 'tolerate' +# end class toleranceType + + +class tolerateType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, startafter=None): + self.original_tagname_ = None + self.startafter = startafter + self.validate_DurationValueType(self.startafter) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, tolerateType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if tolerateType.subclass: + return tolerateType.subclass(*args_, **kwargs_) + else: + return tolerateType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_startafter(self): return self.startafter + def set_startafter(self, startafter): self.startafter = startafter + def validate_DurationValueType(self, value): + # Validate type DurationValueType, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if not self.gds_validate_simple_patterns( + self.validate_DurationValueType_patterns_, value): + warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_DurationValueType_patterns_, )) + validate_DurationValueType_patterns_ = [['^(\\+$|^\\-)?P((\\d+Y)?(\\d+M)?(\\d+D)?T?(\\d+H)?(\\d+M)?(\\d+S)?)$|^(\\d+W)$']] + def hasContent_(self): + if ( + self.startafter is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='tolerateType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('tolerateType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='tolerateType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='tolerateType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='tolerateType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='tolerateType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.startafter is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.startafter), input_name='startafter')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'startafter': + startafter_ = child_.text + startafter_ = self.gds_validate_string(startafter_, node, 'startafter') + self.startafter = startafter_ + # validate type DurationValueType + self.validate_DurationValueType(self.startafter) +# end class tolerateType + + +class eventResponseType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, responseCode=None, responseDescription=None, requestID=None, qualifiedEventID=None, optType=None): + self.original_tagname_ = None + self.responseCode = responseCode + self.responseDescription = responseDescription + self.requestID = requestID + self.qualifiedEventID = qualifiedEventID + self.optType = optType + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eventResponseType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eventResponseType.subclass: + return eventResponseType.subclass(*args_, **kwargs_) + else: + return eventResponseType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_responseCode(self): return self.responseCode + def set_responseCode(self, responseCode): self.responseCode = responseCode + def get_responseDescription(self): return self.responseDescription + def set_responseDescription(self, responseDescription): self.responseDescription = responseDescription + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_qualifiedEventID(self): return self.qualifiedEventID + def set_qualifiedEventID(self, qualifiedEventID): self.qualifiedEventID = qualifiedEventID + def get_optType(self): return self.optType + def set_optType(self, optType): self.optType = optType + def hasContent_(self): + if ( + self.responseCode is not None or + self.responseDescription is not None or + self.requestID is not None or + self.qualifiedEventID is not None or + self.optType is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='eventResponseType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110" xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eventResponseType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eventResponseType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='eventResponseType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='eventResponseType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='eventResponseType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.responseCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.responseCode), input_name='responseCode')), eol_)) + if self.responseDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.responseDescription), input_name='responseDescription')), eol_)) + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.qualifiedEventID is not None: + self.qualifiedEventID.export(outfile, level, namespace_='ei:', name_='qualifiedEventID', pretty_print=pretty_print) + if self.optType is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.optType), input_name='optType')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'responseCode': + responseCode_ = child_.text + responseCode_ = self.gds_validate_string(responseCode_, node, 'responseCode') + self.responseCode = responseCode_ + elif nodeName_ == 'responseDescription': + responseDescription_ = child_.text + responseDescription_ = self.gds_validate_string(responseDescription_, node, 'responseDescription') + self.responseDescription = responseDescription_ + elif nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'qualifiedEventID': + obj_ = QualifiedEventIDType.factory() + obj_.build(child_) + self.qualifiedEventID = obj_ + obj_.original_tagname_ = 'qualifiedEventID' + elif nodeName_ == 'optType': + optType_ = child_.text + if optType_: + optType_ = re_.sub(String_cleanup_pat_, " ", optType_).strip() + else: + optType_ = "" + optType_ = self.gds_validate_string(optType_, node, 'optType') + self.optType = optType_ +# end class eventResponseType + + +class eiMarketContextType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, marketContext=None): + self.original_tagname_ = None + self.marketContext = marketContext + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, eiMarketContextType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if eiMarketContextType.subclass: + return eiMarketContextType.subclass(*args_, **kwargs_) + else: + return eiMarketContextType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_marketContext(self): return self.marketContext + def set_marketContext(self, marketContext): self.marketContext = marketContext + def hasContent_(self): + if ( + self.marketContext is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='eiMarketContextType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06" ', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('eiMarketContextType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='eiMarketContextType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='eiMarketContextType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='eiMarketContextType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='eiMarketContextType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.marketContext is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.marketContext), input_name='marketContext')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'marketContext': + marketContext_ = child_.text + marketContext_ = self.gds_validate_string(marketContext_, node, 'marketContext') + self.marketContext = marketContext_ +# end class eiMarketContextType + + +class locationType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, Polygon=None): + self.original_tagname_ = None + self.Polygon = Polygon + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, locationType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if locationType.subclass: + return locationType.subclass(*args_, **kwargs_) + else: + return locationType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_Polygon(self): return self.Polygon + def set_Polygon(self, Polygon): self.Polygon = Polygon + def hasContent_(self): + if ( + self.Polygon is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='locationType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('locationType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='locationType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='locationType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='locationType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='locationType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.Polygon is not None: + self.Polygon.export(outfile, level, namespace_, name_='Polygon', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'Polygon': + obj_ = PolygonType.factory() + obj_.build(child_) + self.Polygon = obj_ + obj_.original_tagname_ = 'Polygon' +# end class locationType + + +class PolygonType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, id=None, exterior=None): + self.original_tagname_ = None + self.id = _cast(None, id) + self.exterior = exterior + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PolygonType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PolygonType.subclass: + return PolygonType.subclass(*args_, **kwargs_) + else: + return PolygonType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_exterior(self): return self.exterior + def set_exterior(self, exterior): self.exterior = exterior + def get_id(self): return self.id + def set_id(self, id): self.id = id + def hasContent_(self): + if ( + self.exterior is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='PolygonType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PolygonType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PolygonType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='PolygonType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='PolygonType'): + if self.id is not None and 'id' not in already_processed: + already_processed.add('id') + outfile.write(' id=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.id), input_name='id')), )) + def exportChildren(self, outfile, level, namespace_='oadr:', name_='PolygonType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.exterior is not None: + self.exterior.export(outfile, level, namespace_, name_='exterior', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + value = find_attr_value_('id', node) + if value is not None and 'id' not in already_processed: + already_processed.add('id') + self.id = value + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'exterior': + obj_ = exteriorType.factory() + obj_.build(child_) + self.exterior = obj_ + obj_.original_tagname_ = 'exterior' +# end class PolygonType + + +class exteriorType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, LinearRing=None): + self.original_tagname_ = None + self.LinearRing = LinearRing + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, exteriorType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if exteriorType.subclass: + return exteriorType.subclass(*args_, **kwargs_) + else: + return exteriorType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_LinearRing(self): return self.LinearRing + def set_LinearRing(self, LinearRing): self.LinearRing = LinearRing + def hasContent_(self): + if ( + self.LinearRing is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='exteriorType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('exteriorType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='exteriorType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='exteriorType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='exteriorType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='exteriorType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.LinearRing is not None: + self.LinearRing.export(outfile, level, namespace_, name_='LinearRing', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'LinearRing': + obj_ = LinearRingType.factory() + obj_.build(child_) + self.LinearRing = obj_ + obj_.original_tagname_ = 'LinearRing' +# end class exteriorType + + +class LinearRingType(GeneratedsSuper): + subclass = None + superclass = None + def __init__(self, posList=None): + self.original_tagname_ = None + self.posList = posList + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, LinearRingType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if LinearRingType.subclass: + return LinearRingType.subclass(*args_, **kwargs_) + else: + return LinearRingType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_posList(self): return self.posList + def set_posList(self, posList): self.posList = posList + def hasContent_(self): + if ( + self.posList is not None + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='LinearRingType', namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('LinearRingType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='LinearRingType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='LinearRingType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='LinearRingType'): + pass + def exportChildren(self, outfile, level, namespace_='oadr:', name_='LinearRingType', fromsubclass_=False, pretty_print=True): + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.posList is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_double(self.posList, input_name='posList'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + pass + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'posList': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'posList') + self.posList = fval_ +# end class LinearRingType + + +class IdentifiedObject(Object): + """This is a root class to provide common naming attributes for all + classes needing naming attributes""" + subclass = None + superclass = Object + def __init__(self, batchItemInfo=None): + self.original_tagname_ = None + super(IdentifiedObject, self).__init__() + self.batchItemInfo = batchItemInfo + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, IdentifiedObject) + if subclass is not None: + return subclass(*args_, **kwargs_) + if IdentifiedObject.subclass: + return IdentifiedObject.subclass(*args_, **kwargs_) + else: + return IdentifiedObject(*args_, **kwargs_) + factory = staticmethod(factory) + def get_batchItemInfo(self): return self.batchItemInfo + def set_batchItemInfo(self, batchItemInfo): self.batchItemInfo = batchItemInfo + def hasContent_(self): + if ( + self.batchItemInfo is not None or + super(IdentifiedObject, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='IdentifiedObject', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('IdentifiedObject') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='IdentifiedObject') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='IdentifiedObject', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='IdentifiedObject'): + super(IdentifiedObject, self).exportAttributes(outfile, level, already_processed, namespace_, name_='IdentifiedObject') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='IdentifiedObject', fromsubclass_=False, pretty_print=True): + super(IdentifiedObject, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.batchItemInfo is not None: + self.batchItemInfo.export(outfile, level, namespace_, name_='batchItemInfo', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(IdentifiedObject, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'batchItemInfo': + obj_ = BatchItemInfo.factory() + obj_.build(child_) + self.batchItemInfo = obj_ + obj_.original_tagname_ = 'batchItemInfo' + super(IdentifiedObject, self).buildChildren(child_, node, nodeName_, True) +# end class IdentifiedObject + + +class DateTimeInterval(Object): + """Interval of date and time. End is not included because it can be + derived from the start and the duration.""" + subclass = None + superclass = Object + def __init__(self, duration=None, start=None): + self.original_tagname_ = None + super(DateTimeInterval, self).__init__() + self.duration = duration + self.validate_UInt32(self.duration) + self.start = start + self.validate_TimeType(self.start) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, DateTimeInterval) + if subclass is not None: + return subclass(*args_, **kwargs_) + if DateTimeInterval.subclass: + return DateTimeInterval.subclass(*args_, **kwargs_) + else: + return DateTimeInterval(*args_, **kwargs_) + factory = staticmethod(factory) + def get_duration(self): return self.duration + def set_duration(self, duration): self.duration = duration + def get_start(self): return self.start + def set_start(self, start): self.start = start + def validate_UInt32(self, value): + # Validate type UInt32, a restriction on xs:unsignedInt. + if value is not None and Validate_simpletypes_: + pass + def validate_TimeType(self, value): + # Validate type TimeType, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.duration is not None or + self.start is not None or + super(DateTimeInterval, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='DateTimeInterval', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('DateTimeInterval') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='DateTimeInterval') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='DateTimeInterval', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='DateTimeInterval'): + super(DateTimeInterval, self).exportAttributes(outfile, level, already_processed, namespace_, name_='DateTimeInterval') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='DateTimeInterval', fromsubclass_=False, pretty_print=True): + super(DateTimeInterval, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.duration is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.duration, input_name='duration'), eol_)) + if self.start is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.start, input_name='start'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(DateTimeInterval, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'duration': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'duration') + self.duration = ival_ + # validate type UInt32 + self.validate_UInt32(self.duration) + elif nodeName_ == 'start': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'start') + self.start = ival_ + # validate type TimeType + self.validate_TimeType(self.start) + super(DateTimeInterval, self).buildChildren(child_, node, nodeName_, True) +# end class DateTimeInterval + + +class ServiceDeliveryPoint(Object): + """[extension] Service Delivery Point is representation of revenue + UsagePoint attributes""" + subclass = None + superclass = Object + def __init__(self, name=None, tariffProfile=None, customerAgreement=None): + self.original_tagname_ = None + super(ServiceDeliveryPoint, self).__init__() + self.name = name + self.validate_String256(self.name) + self.tariffProfile = tariffProfile + self.validate_String256(self.tariffProfile) + self.customerAgreement = customerAgreement + self.validate_String256(self.customerAgreement) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceDeliveryPoint) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceDeliveryPoint.subclass: + return ServiceDeliveryPoint.subclass(*args_, **kwargs_) + else: + return ServiceDeliveryPoint(*args_, **kwargs_) + factory = staticmethod(factory) + def get_name(self): return self.name + def set_name(self, name): self.name = name + def get_tariffProfile(self): return self.tariffProfile + def set_tariffProfile(self, tariffProfile): self.tariffProfile = tariffProfile + def get_customerAgreement(self): return self.customerAgreement + def set_customerAgreement(self, customerAgreement): self.customerAgreement = customerAgreement + def validate_String256(self, value): + # Validate type String256, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if len(value) > 256: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on String256' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.name is not None or + self.tariffProfile is not None or + self.customerAgreement is not None or + super(ServiceDeliveryPoint, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ServiceDeliveryPoint', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceDeliveryPoint') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceDeliveryPoint') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ServiceDeliveryPoint', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ServiceDeliveryPoint'): + super(ServiceDeliveryPoint, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceDeliveryPoint') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ServiceDeliveryPoint', fromsubclass_=False, pretty_print=True): + super(ServiceDeliveryPoint, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.name is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.name), input_name='name')), eol_)) + if self.tariffProfile is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.tariffProfile), input_name='tariffProfile')), eol_)) + if self.customerAgreement is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.customerAgreement), input_name='customerAgreement')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ServiceDeliveryPoint, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'name': + name_ = child_.text + name_ = self.gds_validate_string(name_, node, 'name') + self.name = name_ + # validate type String256 + self.validate_String256(self.name) + elif nodeName_ == 'tariffProfile': + tariffProfile_ = child_.text + tariffProfile_ = self.gds_validate_string(tariffProfile_, node, 'tariffProfile') + self.tariffProfile = tariffProfile_ + # validate type String256 + self.validate_String256(self.tariffProfile) + elif nodeName_ == 'customerAgreement': + customerAgreement_ = child_.text + customerAgreement_ = self.gds_validate_string(customerAgreement_, node, 'customerAgreement') + self.customerAgreement = customerAgreement_ + # validate type String256 + self.validate_String256(self.customerAgreement) + super(ServiceDeliveryPoint, self).buildChildren(child_, node, nodeName_, True) +# end class ServiceDeliveryPoint + + +class BatchItemInfo(Object): + """Includes elements that make it possible to include multiple + transactions in a single (batch) request.""" + subclass = None + superclass = Object + def __init__(self, name=None, operation=None, statusCode=None, statusReason=None): + self.original_tagname_ = None + super(BatchItemInfo, self).__init__() + self.name = name + self.validate_HexBinary16(self.name) + self.operation = operation + self.validate_CRUDOperation(self.operation) + self.statusCode = statusCode + self.validate_StatusCode(self.statusCode) + self.statusReason = statusReason + self.validate_String256(self.statusReason) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, BatchItemInfo) + if subclass is not None: + return subclass(*args_, **kwargs_) + if BatchItemInfo.subclass: + return BatchItemInfo.subclass(*args_, **kwargs_) + else: + return BatchItemInfo(*args_, **kwargs_) + factory = staticmethod(factory) + def get_name(self): return self.name + def set_name(self, name): self.name = name + def get_operation(self): return self.operation + def set_operation(self, operation): self.operation = operation + def get_statusCode(self): return self.statusCode + def set_statusCode(self, statusCode): self.statusCode = statusCode + def get_statusReason(self): return self.statusReason + def set_statusReason(self, statusReason): self.statusReason = statusReason + def validate_HexBinary16(self, value): + # Validate type HexBinary16, a restriction on xs:hexBinary. + if value is not None and Validate_simpletypes_: + if len(str(value)) > 2: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on HexBinary16' % {"value" : value} ) + def validate_CRUDOperation(self, value): + # Validate type CRUDOperation, a restriction on UInt16. + pass + def validate_StatusCode(self, value): + # Validate type StatusCode, a restriction on UInt16. + pass + def validate_String256(self, value): + # Validate type String256, a restriction on xs:string. + if value is not None and Validate_simpletypes_: + if len(value) > 256: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on String256' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.name is not None or + self.operation is not None or + self.statusCode is not None or + self.statusReason is not None or + super(BatchItemInfo, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='BatchItemInfo', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('BatchItemInfo') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='BatchItemInfo') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='BatchItemInfo', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='BatchItemInfo'): + super(BatchItemInfo, self).exportAttributes(outfile, level, already_processed, namespace_, name_='BatchItemInfo') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='BatchItemInfo', fromsubclass_=False, pretty_print=True): + super(BatchItemInfo, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.name is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.name), input_name='name')), eol_)) + if self.operation is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.operation, input_name='operation'), eol_)) + if self.statusCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.statusCode, input_name='statusCode'), eol_)) + if self.statusReason is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.statusReason), input_name='statusReason')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(BatchItemInfo, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'name': + name_ = child_.text + name_ = self.gds_validate_string(name_, node, 'name') + self.name = name_ + # validate type HexBinary16 + self.validate_HexBinary16(self.name) + elif nodeName_ == 'operation': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'operation') + self.operation = ival_ + # validate type CRUDOperation + self.validate_CRUDOperation(self.operation) + elif nodeName_ == 'statusCode': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'statusCode') + self.statusCode = ival_ + # validate type StatusCode + self.validate_StatusCode(self.statusCode) + elif nodeName_ == 'statusReason': + statusReason_ = child_.text + statusReason_ = self.gds_validate_string(statusReason_, node, 'statusReason') + self.statusReason = statusReason_ + # validate type String256 + self.validate_String256(self.statusReason) + super(BatchItemInfo, self).buildChildren(child_, node, nodeName_, True) +# end class BatchItemInfo + + +class SummaryMeasurement(Object): + """An aggregated summary measurement reading.""" + subclass = None + superclass = Object + def __init__(self, powerOfTenMultiplier=None, timeStamp=None, uom=None, value=None): + self.original_tagname_ = None + super(SummaryMeasurement, self).__init__() + self.powerOfTenMultiplier = powerOfTenMultiplier + self.validate_UnitMultiplierKind(self.powerOfTenMultiplier) + self.timeStamp = timeStamp + self.validate_TimeType(self.timeStamp) + self.uom = uom + self.validate_UnitSymbolKind(self.uom) + self.value = value + self.validate_Int48(self.value) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, SummaryMeasurement) + if subclass is not None: + return subclass(*args_, **kwargs_) + if SummaryMeasurement.subclass: + return SummaryMeasurement.subclass(*args_, **kwargs_) + else: + return SummaryMeasurement(*args_, **kwargs_) + factory = staticmethod(factory) + def get_powerOfTenMultiplier(self): return self.powerOfTenMultiplier + def set_powerOfTenMultiplier(self, powerOfTenMultiplier): self.powerOfTenMultiplier = powerOfTenMultiplier + def get_timeStamp(self): return self.timeStamp + def set_timeStamp(self, timeStamp): self.timeStamp = timeStamp + def get_uom(self): return self.uom + def set_uom(self, uom): self.uom = uom + def get_value(self): return self.value + def set_value(self, value): self.value = value + def validate_UnitMultiplierKind(self, value): + # Validate type UnitMultiplierKind, a restriction on Int16. + pass + def validate_TimeType(self, value): + # Validate type TimeType, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + pass + def validate_UnitSymbolKind(self, value): + # Validate type UnitSymbolKind, a restriction on UInt16. + pass + def validate_Int48(self, value): + # Validate type Int48, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + if value < -140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd minInclusive restriction on Int48' % {"value" : value} ) + if value > 140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd maxInclusive restriction on Int48' % {"value" : value} ) + def hasContent_(self): + if ( + self.powerOfTenMultiplier is not None or + self.timeStamp is not None or + self.uom is not None or + self.value is not None or + super(SummaryMeasurement, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='SummaryMeasurement', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('SummaryMeasurement') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='SummaryMeasurement') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='SummaryMeasurement', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='SummaryMeasurement'): + super(SummaryMeasurement, self).exportAttributes(outfile, level, already_processed, namespace_, name_='SummaryMeasurement') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='SummaryMeasurement', fromsubclass_=False, pretty_print=True): + super(SummaryMeasurement, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.powerOfTenMultiplier is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.powerOfTenMultiplier, input_name='powerOfTenMultiplier'), eol_)) + if self.timeStamp is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.timeStamp, input_name='timeStamp'), eol_)) + if self.uom is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.uom, input_name='uom'), eol_)) + if self.value is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.value, input_name='value'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(SummaryMeasurement, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'powerOfTenMultiplier': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'powerOfTenMultiplier') + self.powerOfTenMultiplier = ival_ + # validate type UnitMultiplierKind + self.validate_UnitMultiplierKind(self.powerOfTenMultiplier) + elif nodeName_ == 'timeStamp': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'timeStamp') + self.timeStamp = ival_ + # validate type TimeType + self.validate_TimeType(self.timeStamp) + elif nodeName_ == 'uom': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'uom') + self.uom = ival_ + # validate type UnitSymbolKind + self.validate_UnitSymbolKind(self.uom) + elif nodeName_ == 'value': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'value') + self.value = ival_ + # validate type Int48 + self.validate_Int48(self.value) + super(SummaryMeasurement, self).buildChildren(child_, node, nodeName_, True) +# end class SummaryMeasurement + + +class ServiceCategory(Object): + """Category of service provided to the customer.""" + subclass = None + superclass = Object + def __init__(self, kind=None): + self.original_tagname_ = None + super(ServiceCategory, self).__init__() + self.kind = kind + self.validate_ServiceKind(self.kind) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ServiceCategory) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ServiceCategory.subclass: + return ServiceCategory.subclass(*args_, **kwargs_) + else: + return ServiceCategory(*args_, **kwargs_) + factory = staticmethod(factory) + def get_kind(self): return self.kind + def set_kind(self, kind): self.kind = kind + def validate_ServiceKind(self, value): + # Validate type ServiceKind, a restriction on UInt16. + pass + def hasContent_(self): + if ( + self.kind is not None or + super(ServiceCategory, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ServiceCategory', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ServiceCategory') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceCategory') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ServiceCategory', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ServiceCategory'): + super(ServiceCategory, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ServiceCategory') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ServiceCategory', fromsubclass_=False, pretty_print=True): + super(ServiceCategory, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.kind is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.kind, input_name='kind'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ServiceCategory, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'kind': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'kind') + self.kind = ival_ + # validate type ServiceKind + self.validate_ServiceKind(self.kind) + super(ServiceCategory, self).buildChildren(child_, node, nodeName_, True) +# end class ServiceCategory + + +class ReadingQuality(Object): + """Quality of a specific reading value or interval reading value. Note + that more than one Quality may be applicable to a given Reading. + Typically not used unless problems or unusual conditions occur + (i.e., quality for each Reading is assumed to be 'Good' (valid) + unless stated otherwise in associated ReadingQuality).""" + subclass = None + superclass = Object + def __init__(self, quality=None): + self.original_tagname_ = None + super(ReadingQuality, self).__init__() + self.quality = quality + self.validate_QualityOfReading(self.quality) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReadingQuality) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReadingQuality.subclass: + return ReadingQuality.subclass(*args_, **kwargs_) + else: + return ReadingQuality(*args_, **kwargs_) + factory = staticmethod(factory) + def get_quality(self): return self.quality + def set_quality(self, quality): self.quality = quality + def validate_QualityOfReading(self, value): + # Validate type QualityOfReading, a restriction on UInt16. + pass + def hasContent_(self): + if ( + self.quality is not None or + super(ReadingQuality, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ReadingQuality', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReadingQuality') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReadingQuality') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ReadingQuality', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ReadingQuality'): + super(ReadingQuality, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ReadingQuality') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ReadingQuality', fromsubclass_=False, pretty_print=True): + super(ReadingQuality, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.quality is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.quality, input_name='quality'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ReadingQuality, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'quality': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'quality') + self.quality = ival_ + # validate type QualityOfReading + self.validate_QualityOfReading(self.quality) + super(ReadingQuality, self).buildChildren(child_, node, nodeName_, True) +# end class ReadingQuality + + +class IntervalReading(Object): + """Specific value measured by a meter or other asset. Each Reading is + associated with a specific ReadingType.""" + subclass = None + superclass = Object + def __init__(self, cost=None, ReadingQuality=None, timePeriod=None, value=None): + self.original_tagname_ = None + super(IntervalReading, self).__init__() + self.cost = cost + self.validate_Int48(self.cost) + if ReadingQuality is None: + self.ReadingQuality = [] + else: + self.ReadingQuality = ReadingQuality + self.timePeriod = timePeriod + self.value = value + self.validate_Int48(self.value) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, IntervalReading) + if subclass is not None: + return subclass(*args_, **kwargs_) + if IntervalReading.subclass: + return IntervalReading.subclass(*args_, **kwargs_) + else: + return IntervalReading(*args_, **kwargs_) + factory = staticmethod(factory) + def get_cost(self): return self.cost + def set_cost(self, cost): self.cost = cost + def get_ReadingQuality(self): return self.ReadingQuality + def set_ReadingQuality(self, ReadingQuality): self.ReadingQuality = ReadingQuality + def add_ReadingQuality(self, value): self.ReadingQuality.append(value) + def insert_ReadingQuality_at(self, index, value): self.ReadingQuality.insert(index, value) + def replace_ReadingQuality_at(self, index, value): self.ReadingQuality[index] = value + def get_timePeriod(self): return self.timePeriod + def set_timePeriod(self, timePeriod): self.timePeriod = timePeriod + def get_value(self): return self.value + def set_value(self, value): self.value = value + def validate_Int48(self, value): + # Validate type Int48, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + if value < -140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd minInclusive restriction on Int48' % {"value" : value} ) + if value > 140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd maxInclusive restriction on Int48' % {"value" : value} ) + def hasContent_(self): + if ( + self.cost is not None or + self.ReadingQuality or + self.timePeriod is not None or + self.value is not None or + super(IntervalReading, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='IntervalReading', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('IntervalReading') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='IntervalReading') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='IntervalReading', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='IntervalReading'): + super(IntervalReading, self).exportAttributes(outfile, level, already_processed, namespace_, name_='IntervalReading') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='IntervalReading', fromsubclass_=False, pretty_print=True): + super(IntervalReading, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.cost is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.cost, input_name='cost'), eol_)) + for ReadingQuality_ in self.ReadingQuality: + ReadingQuality_.export(outfile, level, namespace_, name_='ReadingQuality', pretty_print=pretty_print) + if self.timePeriod is not None: + self.timePeriod.export(outfile, level, namespace_, name_='timePeriod', pretty_print=pretty_print) + if self.value is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.value, input_name='value'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(IntervalReading, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'cost': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'cost') + self.cost = ival_ + # validate type Int48 + self.validate_Int48(self.cost) + elif nodeName_ == 'ReadingQuality': + obj_ = ReadingQuality.factory() + obj_.build(child_) + self.ReadingQuality.append(obj_) + obj_.original_tagname_ = 'ReadingQuality' + elif nodeName_ == 'timePeriod': + obj_ = DateTimeInterval.factory() + obj_.build(child_) + self.timePeriod = obj_ + obj_.original_tagname_ = 'timePeriod' + elif nodeName_ == 'value': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'value') + self.value = ival_ + # validate type Int48 + self.validate_Int48(self.value) + super(IntervalReading, self).buildChildren(child_, node, nodeName_, True) +# end class IntervalReading + + +class TimeConfiguration(IdentifiedObject): + """[extension] Contains attributes related to the configuration of the + time service.""" + subclass = None + superclass = IdentifiedObject + def __init__(self, dstEndRule=None, dstOffset=None, dstStartRule=None, tzOffset=None): + self.original_tagname_ = None + super(TimeConfiguration, self).__init__() + self.dstEndRule = dstEndRule + self.validate_DstRuleType(self.dstEndRule) + self.dstOffset = dstOffset + self.validate_TimeType(self.dstOffset) + self.dstStartRule = dstStartRule + self.validate_DstRuleType(self.dstStartRule) + self.tzOffset = tzOffset + self.validate_TimeType(self.tzOffset) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, TimeConfiguration) + if subclass is not None: + return subclass(*args_, **kwargs_) + if TimeConfiguration.subclass: + return TimeConfiguration.subclass(*args_, **kwargs_) + else: + return TimeConfiguration(*args_, **kwargs_) + factory = staticmethod(factory) + def get_dstEndRule(self): return self.dstEndRule + def set_dstEndRule(self, dstEndRule): self.dstEndRule = dstEndRule + def get_dstOffset(self): return self.dstOffset + def set_dstOffset(self, dstOffset): self.dstOffset = dstOffset + def get_dstStartRule(self): return self.dstStartRule + def set_dstStartRule(self, dstStartRule): self.dstStartRule = dstStartRule + def get_tzOffset(self): return self.tzOffset + def set_tzOffset(self, tzOffset): self.tzOffset = tzOffset + def validate_DstRuleType(self, value): + # Validate type DstRuleType, a restriction on HexBinary32. + if value is not None and Validate_simpletypes_: + if len(str(value)) > 4: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on DstRuleType' % {"value" : value} ) + def validate_TimeType(self, value): + # Validate type TimeType, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.dstEndRule is not None or + self.dstOffset is not None or + self.dstStartRule is not None or + self.tzOffset is not None or + super(TimeConfiguration, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='TimeConfiguration', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('TimeConfiguration') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='TimeConfiguration') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='TimeConfiguration', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='TimeConfiguration'): + super(TimeConfiguration, self).exportAttributes(outfile, level, already_processed, namespace_, name_='TimeConfiguration') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='TimeConfiguration', fromsubclass_=False, pretty_print=True): + super(TimeConfiguration, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.dstEndRule is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.dstEndRule), input_name='dstEndRule')), eol_)) + if self.dstOffset is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.dstOffset, input_name='dstOffset'), eol_)) + if self.dstStartRule is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.dstStartRule), input_name='dstStartRule')), eol_)) + if self.tzOffset is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.tzOffset, input_name='tzOffset'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(TimeConfiguration, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'dstEndRule': + dstEndRule_ = child_.text + dstEndRule_ = self.gds_validate_string(dstEndRule_, node, 'dstEndRule') + self.dstEndRule = dstEndRule_ + # validate type DstRuleType + self.validate_DstRuleType(self.dstEndRule) + elif nodeName_ == 'dstOffset': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'dstOffset') + self.dstOffset = ival_ + # validate type TimeType + self.validate_TimeType(self.dstOffset) + elif nodeName_ == 'dstStartRule': + dstStartRule_ = child_.text + dstStartRule_ = self.gds_validate_string(dstStartRule_, node, 'dstStartRule') + self.dstStartRule = dstStartRule_ + # validate type DstRuleType + self.validate_DstRuleType(self.dstStartRule) + elif nodeName_ == 'tzOffset': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'tzOffset') + self.tzOffset = ival_ + # validate type TimeType + self.validate_TimeType(self.tzOffset) + super(TimeConfiguration, self).buildChildren(child_, node, nodeName_, True) +# end class TimeConfiguration + + +class ElectricPowerUsageSummary(IdentifiedObject): + """Summary of usage for a billing period""" + subclass = None + superclass = IdentifiedObject + def __init__(self, billingPeriod=None, billLastPeriod=None, billToDate=None, costAdditionalLastPeriod=None, costAdditionalDetailLastPeriod=None, currency=None, overallConsumptionLastPeriod=None, currentBillingPeriodOverAllConsumption=None, currentDayLastYearNetConsumption=None, currentDayNetConsumption=None, currentDayOverallConsumption=None, peakDemand=None, previousDayLastYearOverallConsumption=None, previousDayNetConsumption=None, previousDayOverallConsumption=None, qualityOfReading=None, ratchetDemand=None, ratchetDemandPeriod=None, statusTimeStamp=None): + self.original_tagname_ = None + super(ElectricPowerUsageSummary, self).__init__() + self.billingPeriod = billingPeriod + self.billLastPeriod = billLastPeriod + self.validate_Int48(self.billLastPeriod) + self.billToDate = billToDate + self.validate_Int48(self.billToDate) + self.costAdditionalLastPeriod = costAdditionalLastPeriod + self.validate_Int48(self.costAdditionalLastPeriod) + if costAdditionalDetailLastPeriod is None: + self.costAdditionalDetailLastPeriod = [] + else: + self.costAdditionalDetailLastPeriod = costAdditionalDetailLastPeriod + self.currency = currency + self.validate_Currency(self.currency) + self.overallConsumptionLastPeriod = overallConsumptionLastPeriod + self.currentBillingPeriodOverAllConsumption = currentBillingPeriodOverAllConsumption + self.currentDayLastYearNetConsumption = currentDayLastYearNetConsumption + self.currentDayNetConsumption = currentDayNetConsumption + self.currentDayOverallConsumption = currentDayOverallConsumption + self.peakDemand = peakDemand + self.previousDayLastYearOverallConsumption = previousDayLastYearOverallConsumption + self.previousDayNetConsumption = previousDayNetConsumption + self.previousDayOverallConsumption = previousDayOverallConsumption + self.qualityOfReading = qualityOfReading + self.validate_QualityOfReading(self.qualityOfReading) + self.ratchetDemand = ratchetDemand + self.ratchetDemandPeriod = ratchetDemandPeriod + self.statusTimeStamp = statusTimeStamp + self.validate_TimeType(self.statusTimeStamp) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ElectricPowerUsageSummary) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ElectricPowerUsageSummary.subclass: + return ElectricPowerUsageSummary.subclass(*args_, **kwargs_) + else: + return ElectricPowerUsageSummary(*args_, **kwargs_) + factory = staticmethod(factory) + def get_billingPeriod(self): return self.billingPeriod + def set_billingPeriod(self, billingPeriod): self.billingPeriod = billingPeriod + def get_billLastPeriod(self): return self.billLastPeriod + def set_billLastPeriod(self, billLastPeriod): self.billLastPeriod = billLastPeriod + def get_billToDate(self): return self.billToDate + def set_billToDate(self, billToDate): self.billToDate = billToDate + def get_costAdditionalLastPeriod(self): return self.costAdditionalLastPeriod + def set_costAdditionalLastPeriod(self, costAdditionalLastPeriod): self.costAdditionalLastPeriod = costAdditionalLastPeriod + def get_costAdditionalDetailLastPeriod(self): return self.costAdditionalDetailLastPeriod + def set_costAdditionalDetailLastPeriod(self, costAdditionalDetailLastPeriod): self.costAdditionalDetailLastPeriod = costAdditionalDetailLastPeriod + def add_costAdditionalDetailLastPeriod(self, value): self.costAdditionalDetailLastPeriod.append(value) + def insert_costAdditionalDetailLastPeriod_at(self, index, value): self.costAdditionalDetailLastPeriod.insert(index, value) + def replace_costAdditionalDetailLastPeriod_at(self, index, value): self.costAdditionalDetailLastPeriod[index] = value + def get_currency(self): return self.currency + def set_currency(self, currency): self.currency = currency + def get_overallConsumptionLastPeriod(self): return self.overallConsumptionLastPeriod + def set_overallConsumptionLastPeriod(self, overallConsumptionLastPeriod): self.overallConsumptionLastPeriod = overallConsumptionLastPeriod + def get_currentBillingPeriodOverAllConsumption(self): return self.currentBillingPeriodOverAllConsumption + def set_currentBillingPeriodOverAllConsumption(self, currentBillingPeriodOverAllConsumption): self.currentBillingPeriodOverAllConsumption = currentBillingPeriodOverAllConsumption + def get_currentDayLastYearNetConsumption(self): return self.currentDayLastYearNetConsumption + def set_currentDayLastYearNetConsumption(self, currentDayLastYearNetConsumption): self.currentDayLastYearNetConsumption = currentDayLastYearNetConsumption + def get_currentDayNetConsumption(self): return self.currentDayNetConsumption + def set_currentDayNetConsumption(self, currentDayNetConsumption): self.currentDayNetConsumption = currentDayNetConsumption + def get_currentDayOverallConsumption(self): return self.currentDayOverallConsumption + def set_currentDayOverallConsumption(self, currentDayOverallConsumption): self.currentDayOverallConsumption = currentDayOverallConsumption + def get_peakDemand(self): return self.peakDemand + def set_peakDemand(self, peakDemand): self.peakDemand = peakDemand + def get_previousDayLastYearOverallConsumption(self): return self.previousDayLastYearOverallConsumption + def set_previousDayLastYearOverallConsumption(self, previousDayLastYearOverallConsumption): self.previousDayLastYearOverallConsumption = previousDayLastYearOverallConsumption + def get_previousDayNetConsumption(self): return self.previousDayNetConsumption + def set_previousDayNetConsumption(self, previousDayNetConsumption): self.previousDayNetConsumption = previousDayNetConsumption + def get_previousDayOverallConsumption(self): return self.previousDayOverallConsumption + def set_previousDayOverallConsumption(self, previousDayOverallConsumption): self.previousDayOverallConsumption = previousDayOverallConsumption + def get_qualityOfReading(self): return self.qualityOfReading + def set_qualityOfReading(self, qualityOfReading): self.qualityOfReading = qualityOfReading + def get_ratchetDemand(self): return self.ratchetDemand + def set_ratchetDemand(self, ratchetDemand): self.ratchetDemand = ratchetDemand + def get_ratchetDemandPeriod(self): return self.ratchetDemandPeriod + def set_ratchetDemandPeriod(self, ratchetDemandPeriod): self.ratchetDemandPeriod = ratchetDemandPeriod + def get_statusTimeStamp(self): return self.statusTimeStamp + def set_statusTimeStamp(self, statusTimeStamp): self.statusTimeStamp = statusTimeStamp + def validate_Int48(self, value): + # Validate type Int48, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + if value < -140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd minInclusive restriction on Int48' % {"value" : value} ) + if value > 140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd maxInclusive restriction on Int48' % {"value" : value} ) + def validate_Currency(self, value): + # Validate type Currency, a restriction on UInt16. + pass + def validate_QualityOfReading(self, value): + # Validate type QualityOfReading, a restriction on UInt16. + pass + def validate_TimeType(self, value): + # Validate type TimeType, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.billingPeriod is not None or + self.billLastPeriod is not None or + self.billToDate is not None or + self.costAdditionalLastPeriod is not None or + self.costAdditionalDetailLastPeriod or + self.currency is not None or + self.overallConsumptionLastPeriod is not None or + self.currentBillingPeriodOverAllConsumption is not None or + self.currentDayLastYearNetConsumption is not None or + self.currentDayNetConsumption is not None or + self.currentDayOverallConsumption is not None or + self.peakDemand is not None or + self.previousDayLastYearOverallConsumption is not None or + self.previousDayNetConsumption is not None or + self.previousDayOverallConsumption is not None or + self.qualityOfReading is not None or + self.ratchetDemand is not None or + self.ratchetDemandPeriod is not None or + self.statusTimeStamp is not None or + super(ElectricPowerUsageSummary, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ElectricPowerUsageSummary', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ElectricPowerUsageSummary') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ElectricPowerUsageSummary') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ElectricPowerUsageSummary', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ElectricPowerUsageSummary'): + super(ElectricPowerUsageSummary, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ElectricPowerUsageSummary') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ElectricPowerUsageSummary', fromsubclass_=False, pretty_print=True): + super(ElectricPowerUsageSummary, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.billingPeriod is not None: + self.billingPeriod.export(outfile, level, namespace_, name_='billingPeriod', pretty_print=pretty_print) + if self.billLastPeriod is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.billLastPeriod, input_name='billLastPeriod'), eol_)) + if self.billToDate is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.billToDate, input_name='billToDate'), eol_)) + if self.costAdditionalLastPeriod is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.costAdditionalLastPeriod, input_name='costAdditionalLastPeriod'), eol_)) + for costAdditionalDetailLastPeriod_ in self.costAdditionalDetailLastPeriod: + costAdditionalDetailLastPeriod_.export(outfile, level, namespace_, name_='costAdditionalDetailLastPeriod', pretty_print=pretty_print) + if self.currency is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.currency, input_name='currency'), eol_)) + if self.overallConsumptionLastPeriod is not None: + self.overallConsumptionLastPeriod.export(outfile, level, namespace_, name_='overallConsumptionLastPeriod', pretty_print=pretty_print) + if self.currentBillingPeriodOverAllConsumption is not None: + self.currentBillingPeriodOverAllConsumption.export(outfile, level, namespace_, name_='currentBillingPeriodOverAllConsumption', pretty_print=pretty_print) + if self.currentDayLastYearNetConsumption is not None: + self.currentDayLastYearNetConsumption.export(outfile, level, namespace_, name_='currentDayLastYearNetConsumption', pretty_print=pretty_print) + if self.currentDayNetConsumption is not None: + self.currentDayNetConsumption.export(outfile, level, namespace_, name_='currentDayNetConsumption', pretty_print=pretty_print) + if self.currentDayOverallConsumption is not None: + self.currentDayOverallConsumption.export(outfile, level, namespace_, name_='currentDayOverallConsumption', pretty_print=pretty_print) + if self.peakDemand is not None: + self.peakDemand.export(outfile, level, namespace_, name_='peakDemand', pretty_print=pretty_print) + if self.previousDayLastYearOverallConsumption is not None: + self.previousDayLastYearOverallConsumption.export(outfile, level, namespace_, name_='previousDayLastYearOverallConsumption', pretty_print=pretty_print) + if self.previousDayNetConsumption is not None: + self.previousDayNetConsumption.export(outfile, level, namespace_, name_='previousDayNetConsumption', pretty_print=pretty_print) + if self.previousDayOverallConsumption is not None: + self.previousDayOverallConsumption.export(outfile, level, namespace_, name_='previousDayOverallConsumption', pretty_print=pretty_print) + if self.qualityOfReading is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.qualityOfReading, input_name='qualityOfReading'), eol_)) + if self.ratchetDemand is not None: + self.ratchetDemand.export(outfile, level, namespace_, name_='ratchetDemand', pretty_print=pretty_print) + if self.ratchetDemandPeriod is not None: + self.ratchetDemandPeriod.export(outfile, level, namespace_, name_='ratchetDemandPeriod', pretty_print=pretty_print) + if self.statusTimeStamp is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.statusTimeStamp, input_name='statusTimeStamp'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ElectricPowerUsageSummary, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'billingPeriod': + obj_ = DateTimeInterval.factory() + obj_.build(child_) + self.billingPeriod = obj_ + obj_.original_tagname_ = 'billingPeriod' + elif nodeName_ == 'billLastPeriod': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'billLastPeriod') + self.billLastPeriod = ival_ + # validate type Int48 + self.validate_Int48(self.billLastPeriod) + elif nodeName_ == 'billToDate': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'billToDate') + self.billToDate = ival_ + # validate type Int48 + self.validate_Int48(self.billToDate) + elif nodeName_ == 'costAdditionalLastPeriod': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'costAdditionalLastPeriod') + self.costAdditionalLastPeriod = ival_ + # validate type Int48 + self.validate_Int48(self.costAdditionalLastPeriod) + elif nodeName_ == 'costAdditionalDetailLastPeriod': + obj_ = LineItem.factory() + obj_.build(child_) + self.costAdditionalDetailLastPeriod.append(obj_) + obj_.original_tagname_ = 'costAdditionalDetailLastPeriod' + elif nodeName_ == 'currency': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'currency') + self.currency = ival_ + # validate type Currency + self.validate_Currency(self.currency) + elif nodeName_ == 'overallConsumptionLastPeriod': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.overallConsumptionLastPeriod = obj_ + obj_.original_tagname_ = 'overallConsumptionLastPeriod' + elif nodeName_ == 'currentBillingPeriodOverAllConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.currentBillingPeriodOverAllConsumption = obj_ + obj_.original_tagname_ = 'currentBillingPeriodOverAllConsumption' + elif nodeName_ == 'currentDayLastYearNetConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.currentDayLastYearNetConsumption = obj_ + obj_.original_tagname_ = 'currentDayLastYearNetConsumption' + elif nodeName_ == 'currentDayNetConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.currentDayNetConsumption = obj_ + obj_.original_tagname_ = 'currentDayNetConsumption' + elif nodeName_ == 'currentDayOverallConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.currentDayOverallConsumption = obj_ + obj_.original_tagname_ = 'currentDayOverallConsumption' + elif nodeName_ == 'peakDemand': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.peakDemand = obj_ + obj_.original_tagname_ = 'peakDemand' + elif nodeName_ == 'previousDayLastYearOverallConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.previousDayLastYearOverallConsumption = obj_ + obj_.original_tagname_ = 'previousDayLastYearOverallConsumption' + elif nodeName_ == 'previousDayNetConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.previousDayNetConsumption = obj_ + obj_.original_tagname_ = 'previousDayNetConsumption' + elif nodeName_ == 'previousDayOverallConsumption': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.previousDayOverallConsumption = obj_ + obj_.original_tagname_ = 'previousDayOverallConsumption' + elif nodeName_ == 'qualityOfReading': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'qualityOfReading') + self.qualityOfReading = ival_ + # validate type QualityOfReading + self.validate_QualityOfReading(self.qualityOfReading) + elif nodeName_ == 'ratchetDemand': + obj_ = SummaryMeasurement.factory() + obj_.build(child_) + self.ratchetDemand = obj_ + obj_.original_tagname_ = 'ratchetDemand' + elif nodeName_ == 'ratchetDemandPeriod': + obj_ = DateTimeInterval.factory() + obj_.build(child_) + self.ratchetDemandPeriod = obj_ + obj_.original_tagname_ = 'ratchetDemandPeriod' + elif nodeName_ == 'statusTimeStamp': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'statusTimeStamp') + self.statusTimeStamp = ival_ + # validate type TimeType + self.validate_TimeType(self.statusTimeStamp) + super(ElectricPowerUsageSummary, self).buildChildren(child_, node, nodeName_, True) +# end class ElectricPowerUsageSummary + + +class ElectricPowerQualitySummary(IdentifiedObject): + """A summary of power quality events. This information represents a + summary of power quality information typically required by + customer facility energy management systems. It is not intended + to satisfy the detailed requirements of power quality + monitoring. All values are as defined by measurementProtocol + during the period. The standards typically also give ranges of + allowed values; the information attributes are the raw + measurements, not the "yes/no" determination by the various + standards. See referenced standards for definition, measurement + protocol and period.""" + subclass = None + superclass = IdentifiedObject + def __init__(self, flickerPlt=None, flickerPst=None, harmonicVoltage=None, longInterruptions=None, mainsVoltage=None, measurementProtocol=None, powerFrequency=None, rapidVoltageChanges=None, shortInterruptions=None, summaryInterval=None, supplyVoltageDips=None, supplyVoltageImbalance=None, supplyVoltageVariations=None, tempOvervoltage=None): + self.original_tagname_ = None + super(ElectricPowerQualitySummary, self).__init__() + self.flickerPlt = flickerPlt + self.validate_Int48(self.flickerPlt) + self.flickerPst = flickerPst + self.validate_Int48(self.flickerPst) + self.harmonicVoltage = harmonicVoltage + self.validate_Int48(self.harmonicVoltage) + self.longInterruptions = longInterruptions + self.validate_Int48(self.longInterruptions) + self.mainsVoltage = mainsVoltage + self.validate_Int48(self.mainsVoltage) + self.measurementProtocol = measurementProtocol + self.validate_UInt8(self.measurementProtocol) + self.powerFrequency = powerFrequency + self.validate_Int48(self.powerFrequency) + self.rapidVoltageChanges = rapidVoltageChanges + self.validate_Int48(self.rapidVoltageChanges) + self.shortInterruptions = shortInterruptions + self.validate_Int48(self.shortInterruptions) + self.summaryInterval = summaryInterval + self.supplyVoltageDips = supplyVoltageDips + self.validate_Int48(self.supplyVoltageDips) + self.supplyVoltageImbalance = supplyVoltageImbalance + self.validate_Int48(self.supplyVoltageImbalance) + self.supplyVoltageVariations = supplyVoltageVariations + self.validate_Int48(self.supplyVoltageVariations) + self.tempOvervoltage = tempOvervoltage + self.validate_Int48(self.tempOvervoltage) + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ElectricPowerQualitySummary) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ElectricPowerQualitySummary.subclass: + return ElectricPowerQualitySummary.subclass(*args_, **kwargs_) + else: + return ElectricPowerQualitySummary(*args_, **kwargs_) + factory = staticmethod(factory) + def get_flickerPlt(self): return self.flickerPlt + def set_flickerPlt(self, flickerPlt): self.flickerPlt = flickerPlt + def get_flickerPst(self): return self.flickerPst + def set_flickerPst(self, flickerPst): self.flickerPst = flickerPst + def get_harmonicVoltage(self): return self.harmonicVoltage + def set_harmonicVoltage(self, harmonicVoltage): self.harmonicVoltage = harmonicVoltage + def get_longInterruptions(self): return self.longInterruptions + def set_longInterruptions(self, longInterruptions): self.longInterruptions = longInterruptions + def get_mainsVoltage(self): return self.mainsVoltage + def set_mainsVoltage(self, mainsVoltage): self.mainsVoltage = mainsVoltage + def get_measurementProtocol(self): return self.measurementProtocol + def set_measurementProtocol(self, measurementProtocol): self.measurementProtocol = measurementProtocol + def get_powerFrequency(self): return self.powerFrequency + def set_powerFrequency(self, powerFrequency): self.powerFrequency = powerFrequency + def get_rapidVoltageChanges(self): return self.rapidVoltageChanges + def set_rapidVoltageChanges(self, rapidVoltageChanges): self.rapidVoltageChanges = rapidVoltageChanges + def get_shortInterruptions(self): return self.shortInterruptions + def set_shortInterruptions(self, shortInterruptions): self.shortInterruptions = shortInterruptions + def get_summaryInterval(self): return self.summaryInterval + def set_summaryInterval(self, summaryInterval): self.summaryInterval = summaryInterval + def get_supplyVoltageDips(self): return self.supplyVoltageDips + def set_supplyVoltageDips(self, supplyVoltageDips): self.supplyVoltageDips = supplyVoltageDips + def get_supplyVoltageImbalance(self): return self.supplyVoltageImbalance + def set_supplyVoltageImbalance(self, supplyVoltageImbalance): self.supplyVoltageImbalance = supplyVoltageImbalance + def get_supplyVoltageVariations(self): return self.supplyVoltageVariations + def set_supplyVoltageVariations(self, supplyVoltageVariations): self.supplyVoltageVariations = supplyVoltageVariations + def get_tempOvervoltage(self): return self.tempOvervoltage + def set_tempOvervoltage(self, tempOvervoltage): self.tempOvervoltage = tempOvervoltage + def validate_Int48(self, value): + # Validate type Int48, a restriction on xs:long. + if value is not None and Validate_simpletypes_: + if value < -140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd minInclusive restriction on Int48' % {"value" : value} ) + if value > 140737488355328: + warnings_.warn('Value "%(value)s" does not match xsd maxInclusive restriction on Int48' % {"value" : value} ) + def validate_UInt8(self, value): + # Validate type UInt8, a restriction on xs:unsignedByte. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.flickerPlt is not None or + self.flickerPst is not None or + self.harmonicVoltage is not None or + self.longInterruptions is not None or + self.mainsVoltage is not None or + self.measurementProtocol is not None or + self.powerFrequency is not None or + self.rapidVoltageChanges is not None or + self.shortInterruptions is not None or + self.summaryInterval is not None or + self.supplyVoltageDips is not None or + self.supplyVoltageImbalance is not None or + self.supplyVoltageVariations is not None or + self.tempOvervoltage is not None or + super(ElectricPowerQualitySummary, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ElectricPowerQualitySummary', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ElectricPowerQualitySummary') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ElectricPowerQualitySummary') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ElectricPowerQualitySummary', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ElectricPowerQualitySummary'): + super(ElectricPowerQualitySummary, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ElectricPowerQualitySummary') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ElectricPowerQualitySummary', fromsubclass_=False, pretty_print=True): + super(ElectricPowerQualitySummary, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.flickerPlt is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.flickerPlt, input_name='flickerPlt'), eol_)) + if self.flickerPst is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.flickerPst, input_name='flickerPst'), eol_)) + if self.harmonicVoltage is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.harmonicVoltage, input_name='harmonicVoltage'), eol_)) + if self.longInterruptions is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.longInterruptions, input_name='longInterruptions'), eol_)) + if self.mainsVoltage is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.mainsVoltage, input_name='mainsVoltage'), eol_)) + if self.measurementProtocol is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.measurementProtocol, input_name='measurementProtocol'), eol_)) + if self.powerFrequency is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.powerFrequency, input_name='powerFrequency'), eol_)) + if self.rapidVoltageChanges is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.rapidVoltageChanges, input_name='rapidVoltageChanges'), eol_)) + if self.shortInterruptions is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.shortInterruptions, input_name='shortInterruptions'), eol_)) + if self.summaryInterval is not None: + self.summaryInterval.export(outfile, level, namespace_, name_='summaryInterval', pretty_print=pretty_print) + if self.supplyVoltageDips is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.supplyVoltageDips, input_name='supplyVoltageDips'), eol_)) + if self.supplyVoltageImbalance is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.supplyVoltageImbalance, input_name='supplyVoltageImbalance'), eol_)) + if self.supplyVoltageVariations is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.supplyVoltageVariations, input_name='supplyVoltageVariations'), eol_)) + if self.tempOvervoltage is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.tempOvervoltage, input_name='tempOvervoltage'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ElectricPowerQualitySummary, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'flickerPlt': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'flickerPlt') + self.flickerPlt = ival_ + # validate type Int48 + self.validate_Int48(self.flickerPlt) + elif nodeName_ == 'flickerPst': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'flickerPst') + self.flickerPst = ival_ + # validate type Int48 + self.validate_Int48(self.flickerPst) + elif nodeName_ == 'harmonicVoltage': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'harmonicVoltage') + self.harmonicVoltage = ival_ + # validate type Int48 + self.validate_Int48(self.harmonicVoltage) + elif nodeName_ == 'longInterruptions': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'longInterruptions') + self.longInterruptions = ival_ + # validate type Int48 + self.validate_Int48(self.longInterruptions) + elif nodeName_ == 'mainsVoltage': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'mainsVoltage') + self.mainsVoltage = ival_ + # validate type Int48 + self.validate_Int48(self.mainsVoltage) + elif nodeName_ == 'measurementProtocol': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'measurementProtocol') + self.measurementProtocol = ival_ + # validate type UInt8 + self.validate_UInt8(self.measurementProtocol) + elif nodeName_ == 'powerFrequency': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'powerFrequency') + self.powerFrequency = ival_ + # validate type Int48 + self.validate_Int48(self.powerFrequency) + elif nodeName_ == 'rapidVoltageChanges': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'rapidVoltageChanges') + self.rapidVoltageChanges = ival_ + # validate type Int48 + self.validate_Int48(self.rapidVoltageChanges) + elif nodeName_ == 'shortInterruptions': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'shortInterruptions') + self.shortInterruptions = ival_ + # validate type Int48 + self.validate_Int48(self.shortInterruptions) + elif nodeName_ == 'summaryInterval': + obj_ = DateTimeInterval.factory() + obj_.build(child_) + self.summaryInterval = obj_ + obj_.original_tagname_ = 'summaryInterval' + elif nodeName_ == 'supplyVoltageDips': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'supplyVoltageDips') + self.supplyVoltageDips = ival_ + # validate type Int48 + self.validate_Int48(self.supplyVoltageDips) + elif nodeName_ == 'supplyVoltageImbalance': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'supplyVoltageImbalance') + self.supplyVoltageImbalance = ival_ + # validate type Int48 + self.validate_Int48(self.supplyVoltageImbalance) + elif nodeName_ == 'supplyVoltageVariations': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'supplyVoltageVariations') + self.supplyVoltageVariations = ival_ + # validate type Int48 + self.validate_Int48(self.supplyVoltageVariations) + elif nodeName_ == 'tempOvervoltage': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'tempOvervoltage') + self.tempOvervoltage = ival_ + # validate type Int48 + self.validate_Int48(self.tempOvervoltage) + super(ElectricPowerQualitySummary, self).buildChildren(child_, node, nodeName_, True) +# end class ElectricPowerQualitySummary + + +class UsagePoint(IdentifiedObject): + """Logical point on a network at which consumption or production is + either physically measured (e.g., metered) or estimated (e.g., + unmetered street lights).""" + subclass = None + superclass = IdentifiedObject + def __init__(self, roleFlags=None, ServiceCategory=None, status=None, ServiceDeliveryPoint=None): + self.original_tagname_ = None + super(UsagePoint, self).__init__() + self.roleFlags = roleFlags + self.validate_HexBinary16(self.roleFlags) + self.ServiceCategory = ServiceCategory + self.status = status + self.validate_UInt8(self.status) + self.ServiceDeliveryPoint = ServiceDeliveryPoint + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, UsagePoint) + if subclass is not None: + return subclass(*args_, **kwargs_) + if UsagePoint.subclass: + return UsagePoint.subclass(*args_, **kwargs_) + else: + return UsagePoint(*args_, **kwargs_) + factory = staticmethod(factory) + def get_roleFlags(self): return self.roleFlags + def set_roleFlags(self, roleFlags): self.roleFlags = roleFlags + def get_ServiceCategory(self): return self.ServiceCategory + def set_ServiceCategory(self, ServiceCategory): self.ServiceCategory = ServiceCategory + def get_status(self): return self.status + def set_status(self, status): self.status = status + def get_ServiceDeliveryPoint(self): return self.ServiceDeliveryPoint + def set_ServiceDeliveryPoint(self, ServiceDeliveryPoint): self.ServiceDeliveryPoint = ServiceDeliveryPoint + def validate_HexBinary16(self, value): + # Validate type HexBinary16, a restriction on xs:hexBinary. + if value is not None and Validate_simpletypes_: + if len(str(value)) > 2: + warnings_.warn('Value "%(value)s" does not match xsd maxLength restriction on HexBinary16' % {"value" : value} ) + def validate_UInt8(self, value): + # Validate type UInt8, a restriction on xs:unsignedByte. + if value is not None and Validate_simpletypes_: + pass + def hasContent_(self): + if ( + self.roleFlags is not None or + self.ServiceCategory is not None or + self.status is not None or + self.ServiceDeliveryPoint is not None or + super(UsagePoint, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='UsagePoint', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('UsagePoint') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='UsagePoint') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='UsagePoint', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='UsagePoint'): + super(UsagePoint, self).exportAttributes(outfile, level, already_processed, namespace_, name_='UsagePoint') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='UsagePoint', fromsubclass_=False, pretty_print=True): + super(UsagePoint, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.roleFlags is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.roleFlags), input_name='roleFlags')), eol_)) + if self.ServiceCategory is not None: + self.ServiceCategory.export(outfile, level, namespace_, name_='ServiceCategory', pretty_print=pretty_print) + if self.status is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.status, input_name='status'), eol_)) + if self.ServiceDeliveryPoint is not None: + self.ServiceDeliveryPoint.export(outfile, level, namespace_, name_='ServiceDeliveryPoint', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(UsagePoint, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'roleFlags': + roleFlags_ = child_.text + roleFlags_ = self.gds_validate_string(roleFlags_, node, 'roleFlags') + self.roleFlags = roleFlags_ + # validate type HexBinary16 + self.validate_HexBinary16(self.roleFlags) + elif nodeName_ == 'ServiceCategory': + obj_ = ServiceCategory.factory() + obj_.build(child_) + self.ServiceCategory = obj_ + obj_.original_tagname_ = 'ServiceCategory' + elif nodeName_ == 'status': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'status') + self.status = ival_ + # validate type UInt8 + self.validate_UInt8(self.status) + elif nodeName_ == 'ServiceDeliveryPoint': + obj_ = ServiceDeliveryPoint.factory() + obj_.build(child_) + self.ServiceDeliveryPoint = obj_ + obj_.original_tagname_ = 'ServiceDeliveryPoint' + super(UsagePoint, self).buildChildren(child_, node, nodeName_, True) +# end class UsagePoint + + +class ReadingType(IdentifiedObject): + """Characteristics associated with all Readings included in a + MeterReading.""" + subclass = None + superclass = IdentifiedObject + def __init__(self, accumulationBehaviour=None, commodity=None, consumptionTier=None, currency=None, dataQualifier=None, defaultQuality=None, flowDirection=None, intervalLength=None, kind=None, phase=None, powerOfTenMultiplier=None, timeAttribute=None, tou=None, uom=None, cpp=None, interharmonic=None, measuringPeriod=None, argument=None): + self.original_tagname_ = None + super(ReadingType, self).__init__() + self.accumulationBehaviour = accumulationBehaviour + self.validate_AccumulationKind(self.accumulationBehaviour) + self.commodity = commodity + self.validate_CommodityKind(self.commodity) + self.consumptionTier = consumptionTier + self.validate_Int16(self.consumptionTier) + self.currency = currency + self.validate_Currency(self.currency) + self.dataQualifier = dataQualifier + self.validate_DataQualifierKind(self.dataQualifier) + self.defaultQuality = defaultQuality + self.validate_QualityOfReading(self.defaultQuality) + self.flowDirection = flowDirection + self.validate_FlowDirectionKind(self.flowDirection) + self.intervalLength = intervalLength + self.validate_UInt32(self.intervalLength) + self.kind = kind + self.validate_MeasurementKind(self.kind) + self.phase = phase + self.validate_PhaseCodeKind(self.phase) + self.powerOfTenMultiplier = powerOfTenMultiplier + self.validate_UnitMultiplierKind(self.powerOfTenMultiplier) + self.timeAttribute = timeAttribute + self.validate_TimePeriodOfInterest(self.timeAttribute) + self.tou = tou + self.validate_Int16(self.tou) + self.uom = uom + self.validate_UnitSymbolKind(self.uom) + self.cpp = cpp + self.validate_Int16(self.cpp) + self.interharmonic = interharmonic + self.measuringPeriod = measuringPeriod + self.validate_TimeAttributeKind(self.measuringPeriod) + self.argument = argument + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ReadingType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ReadingType.subclass: + return ReadingType.subclass(*args_, **kwargs_) + else: + return ReadingType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_accumulationBehaviour(self): return self.accumulationBehaviour + def set_accumulationBehaviour(self, accumulationBehaviour): self.accumulationBehaviour = accumulationBehaviour + def get_commodity(self): return self.commodity + def set_commodity(self, commodity): self.commodity = commodity + def get_consumptionTier(self): return self.consumptionTier + def set_consumptionTier(self, consumptionTier): self.consumptionTier = consumptionTier + def get_currency(self): return self.currency + def set_currency(self, currency): self.currency = currency + def get_dataQualifier(self): return self.dataQualifier + def set_dataQualifier(self, dataQualifier): self.dataQualifier = dataQualifier + def get_defaultQuality(self): return self.defaultQuality + def set_defaultQuality(self, defaultQuality): self.defaultQuality = defaultQuality + def get_flowDirection(self): return self.flowDirection + def set_flowDirection(self, flowDirection): self.flowDirection = flowDirection + def get_intervalLength(self): return self.intervalLength + def set_intervalLength(self, intervalLength): self.intervalLength = intervalLength + def get_kind(self): return self.kind + def set_kind(self, kind): self.kind = kind + def get_phase(self): return self.phase + def set_phase(self, phase): self.phase = phase + def get_powerOfTenMultiplier(self): return self.powerOfTenMultiplier + def set_powerOfTenMultiplier(self, powerOfTenMultiplier): self.powerOfTenMultiplier = powerOfTenMultiplier + def get_timeAttribute(self): return self.timeAttribute + def set_timeAttribute(self, timeAttribute): self.timeAttribute = timeAttribute + def get_tou(self): return self.tou + def set_tou(self, tou): self.tou = tou + def get_uom(self): return self.uom + def set_uom(self, uom): self.uom = uom + def get_cpp(self): return self.cpp + def set_cpp(self, cpp): self.cpp = cpp + def get_interharmonic(self): return self.interharmonic + def set_interharmonic(self, interharmonic): self.interharmonic = interharmonic + def get_measuringPeriod(self): return self.measuringPeriod + def set_measuringPeriod(self, measuringPeriod): self.measuringPeriod = measuringPeriod + def get_argument(self): return self.argument + def set_argument(self, argument): self.argument = argument + def validate_AccumulationKind(self, value): + # Validate type AccumulationKind, a restriction on UInt16. + pass + def validate_CommodityKind(self, value): + # Validate type CommodityKind, a restriction on UInt16. + pass + def validate_Int16(self, value): + # Validate type Int16, a restriction on xs:short. + if value is not None and Validate_simpletypes_: + pass + def validate_Currency(self, value): + # Validate type Currency, a restriction on UInt16. + pass + def validate_DataQualifierKind(self, value): + # Validate type DataQualifierKind, a restriction on UInt16. + pass + def validate_QualityOfReading(self, value): + # Validate type QualityOfReading, a restriction on UInt16. + pass + def validate_FlowDirectionKind(self, value): + # Validate type FlowDirectionKind, a restriction on UInt16. + pass + def validate_UInt32(self, value): + # Validate type UInt32, a restriction on xs:unsignedInt. + if value is not None and Validate_simpletypes_: + pass + def validate_MeasurementKind(self, value): + # Validate type MeasurementKind, a restriction on UInt16. + pass + def validate_PhaseCodeKind(self, value): + # Validate type PhaseCodeKind, a restriction on UInt16. + pass + def validate_UnitMultiplierKind(self, value): + # Validate type UnitMultiplierKind, a restriction on Int16. + pass + def validate_TimePeriodOfInterest(self, value): + # Validate type TimePeriodOfInterest, a restriction on UInt16. + pass + def validate_UnitSymbolKind(self, value): + # Validate type UnitSymbolKind, a restriction on UInt16. + pass + def validate_TimeAttributeKind(self, value): + # Validate type TimeAttributeKind, a restriction on UInt16. + pass + def hasContent_(self): + if ( + self.accumulationBehaviour is not None or + self.commodity is not None or + self.consumptionTier is not None or + self.currency is not None or + self.dataQualifier is not None or + self.defaultQuality is not None or + self.flowDirection is not None or + self.intervalLength is not None or + self.kind is not None or + self.phase is not None or + self.powerOfTenMultiplier is not None or + self.timeAttribute is not None or + self.tou is not None or + self.uom is not None or + self.cpp is not None or + self.interharmonic is not None or + self.measuringPeriod is not None or + self.argument is not None or + super(ReadingType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ReadingType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ReadingType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ReadingType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ReadingType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ReadingType'): + super(ReadingType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ReadingType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ReadingType', fromsubclass_=False, pretty_print=True): + super(ReadingType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.accumulationBehaviour is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.accumulationBehaviour, input_name='accumulationBehaviour'), eol_)) + if self.commodity is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.commodity, input_name='commodity'), eol_)) + if self.consumptionTier is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.consumptionTier, input_name='consumptionTier'), eol_)) + if self.currency is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.currency, input_name='currency'), eol_)) + if self.dataQualifier is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.dataQualifier, input_name='dataQualifier'), eol_)) + if self.defaultQuality is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.defaultQuality, input_name='defaultQuality'), eol_)) + if self.flowDirection is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.flowDirection, input_name='flowDirection'), eol_)) + if self.intervalLength is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.intervalLength, input_name='intervalLength'), eol_)) + if self.kind is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.kind, input_name='kind'), eol_)) + if self.phase is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.phase, input_name='phase'), eol_)) + if self.powerOfTenMultiplier is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.powerOfTenMultiplier, input_name='powerOfTenMultiplier'), eol_)) + if self.timeAttribute is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.timeAttribute, input_name='timeAttribute'), eol_)) + if self.tou is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.tou, input_name='tou'), eol_)) + if self.uom is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.uom, input_name='uom'), eol_)) + if self.cpp is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.cpp, input_name='cpp'), eol_)) + if self.interharmonic is not None: + self.interharmonic.export(outfile, level, namespace_, name_='interharmonic', pretty_print=pretty_print) + if self.measuringPeriod is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_integer(self.measuringPeriod, input_name='measuringPeriod'), eol_)) + if self.argument is not None: + self.argument.export(outfile, level, namespace_, name_='argument', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ReadingType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'accumulationBehaviour': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'accumulationBehaviour') + self.accumulationBehaviour = ival_ + # validate type AccumulationKind + self.validate_AccumulationKind(self.accumulationBehaviour) + elif nodeName_ == 'commodity': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'commodity') + self.commodity = ival_ + # validate type CommodityKind + self.validate_CommodityKind(self.commodity) + elif nodeName_ == 'consumptionTier': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'consumptionTier') + self.consumptionTier = ival_ + # validate type Int16 + self.validate_Int16(self.consumptionTier) + elif nodeName_ == 'currency': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'currency') + self.currency = ival_ + # validate type Currency + self.validate_Currency(self.currency) + elif nodeName_ == 'dataQualifier': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'dataQualifier') + self.dataQualifier = ival_ + # validate type DataQualifierKind + self.validate_DataQualifierKind(self.dataQualifier) + elif nodeName_ == 'defaultQuality': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'defaultQuality') + self.defaultQuality = ival_ + # validate type QualityOfReading + self.validate_QualityOfReading(self.defaultQuality) + elif nodeName_ == 'flowDirection': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'flowDirection') + self.flowDirection = ival_ + # validate type FlowDirectionKind + self.validate_FlowDirectionKind(self.flowDirection) + elif nodeName_ == 'intervalLength': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'intervalLength') + self.intervalLength = ival_ + # validate type UInt32 + self.validate_UInt32(self.intervalLength) + elif nodeName_ == 'kind': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'kind') + self.kind = ival_ + # validate type MeasurementKind + self.validate_MeasurementKind(self.kind) + elif nodeName_ == 'phase': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'phase') + self.phase = ival_ + # validate type PhaseCodeKind + self.validate_PhaseCodeKind(self.phase) + elif nodeName_ == 'powerOfTenMultiplier': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'powerOfTenMultiplier') + self.powerOfTenMultiplier = ival_ + # validate type UnitMultiplierKind + self.validate_UnitMultiplierKind(self.powerOfTenMultiplier) + elif nodeName_ == 'timeAttribute': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'timeAttribute') + self.timeAttribute = ival_ + # validate type TimePeriodOfInterest + self.validate_TimePeriodOfInterest(self.timeAttribute) + elif nodeName_ == 'tou': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'tou') + self.tou = ival_ + # validate type Int16 + self.validate_Int16(self.tou) + elif nodeName_ == 'uom': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'uom') + self.uom = ival_ + # validate type UnitSymbolKind + self.validate_UnitSymbolKind(self.uom) + elif nodeName_ == 'cpp': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'cpp') + self.cpp = ival_ + # validate type Int16 + self.validate_Int16(self.cpp) + elif nodeName_ == 'interharmonic': + obj_ = ReadingInterharmonic.factory() + obj_.build(child_) + self.interharmonic = obj_ + obj_.original_tagname_ = 'interharmonic' + elif nodeName_ == 'measuringPeriod': + sval_ = child_.text + try: + ival_ = int(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires integer: %s' % exp) + ival_ = self.gds_validate_integer(ival_, node, 'measuringPeriod') + self.measuringPeriod = ival_ + # validate type TimeAttributeKind + self.validate_TimeAttributeKind(self.measuringPeriod) + elif nodeName_ == 'argument': + obj_ = RationalNumber.factory() + obj_.build(child_) + self.argument = obj_ + obj_.original_tagname_ = 'argument' + super(ReadingType, self).buildChildren(child_, node, nodeName_, True) +# end class ReadingType + + +class MeterReading(IdentifiedObject): + """Set of values obtained from the meter.""" + subclass = None + superclass = IdentifiedObject + def __init__(self): + self.original_tagname_ = None + super(MeterReading, self).__init__() + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, MeterReading) + if subclass is not None: + return subclass(*args_, **kwargs_) + if MeterReading.subclass: + return MeterReading.subclass(*args_, **kwargs_) + else: + return MeterReading(*args_, **kwargs_) + factory = staticmethod(factory) + def hasContent_(self): + if ( + super(MeterReading, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='MeterReading', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('MeterReading') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='MeterReading') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='MeterReading', pretty_print=pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='MeterReading'): + super(MeterReading, self).exportAttributes(outfile, level, already_processed, namespace_, name_='MeterReading') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='MeterReading', fromsubclass_=False, pretty_print=True): + super(MeterReading, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + pass + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(MeterReading, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + super(MeterReading, self).buildChildren(child_, node, nodeName_, True) + pass +# end class MeterReading + + +class IntervalBlock(IdentifiedObject): + """Time sequence of Readings of the same ReadingType.""" + subclass = None + superclass = IdentifiedObject + def __init__(self, interval=None, IntervalReading=None): + self.original_tagname_ = None + super(IntervalBlock, self).__init__() + self.interval = interval + if IntervalReading is None: + self.IntervalReading = [] + else: + self.IntervalReading = IntervalReading + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, IntervalBlock) + if subclass is not None: + return subclass(*args_, **kwargs_) + if IntervalBlock.subclass: + return IntervalBlock.subclass(*args_, **kwargs_) + else: + return IntervalBlock(*args_, **kwargs_) + factory = staticmethod(factory) + def get_interval(self): return self.interval + def set_interval(self, interval): self.interval = interval + def get_IntervalReading(self): return self.IntervalReading + def set_IntervalReading(self, IntervalReading): self.IntervalReading = IntervalReading + def add_IntervalReading(self, value): self.IntervalReading.append(value) + def insert_IntervalReading_at(self, index, value): self.IntervalReading.insert(index, value) + def replace_IntervalReading_at(self, index, value): self.IntervalReading[index] = value + def hasContent_(self): + if ( + self.interval is not None or + self.IntervalReading or + super(IntervalBlock, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='IntervalBlock', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('IntervalBlock') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='IntervalBlock') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='IntervalBlock', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='IntervalBlock'): + super(IntervalBlock, self).exportAttributes(outfile, level, already_processed, namespace_, name_='IntervalBlock') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='IntervalBlock', fromsubclass_=False, pretty_print=True): + super(IntervalBlock, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.interval is not None: + self.interval.export(outfile, level, namespace_, name_='interval', pretty_print=pretty_print) + for IntervalReading_ in self.IntervalReading: + IntervalReading_.export(outfile, level, namespace_, name_='IntervalReading', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(IntervalBlock, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'interval': + obj_ = IntervalType.factory() + obj_.build(child_) + self.interval = obj_ + obj_.original_tagname_ = 'interval' + elif nodeName_ == 'IntervalReading': + obj_ = IntervalReading.factory() + obj_.build(child_) + self.IntervalReading.append(obj_) + obj_.original_tagname_ = 'IntervalReading' + super(IntervalBlock, self).buildChildren(child_, node, nodeName_, True) +# end class IntervalBlock + + +class PowerItemType(ItemBaseType): + """Base for the measurement of Power""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None, powerAttributes=None): + self.original_tagname_ = None + super(PowerItemType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + self.powerAttributes = powerAttributes + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, PowerItemType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if PowerItemType.subclass: + return PowerItemType.subclass(*args_, **kwargs_) + else: + return PowerItemType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def get_powerAttributes(self): return self.powerAttributes + def set_powerAttributes(self, powerAttributes): self.powerAttributes = powerAttributes + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + self.powerAttributes is not None or + super(PowerItemType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='PowerItemType', namespacedef_=' xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('PowerItemType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='PowerItemType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='PowerItemType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='PowerItemType'): + super(PowerItemType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='PowerItemType') + def exportChildren(self, outfile, level, namespace_='power:', name_='PowerItemType', fromsubclass_=False, pretty_print=True): + super(PowerItemType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + if self.powerAttributes is not None: + self.powerAttributes.export(outfile, level, namespace_='power:', name_='powerAttributes', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(PowerItemType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + elif nodeName_ == 'powerAttributes': + obj_ = PowerAttributesType.factory() + obj_.build(child_) + self.powerAttributes = obj_ + obj_.original_tagname_ = 'powerAttributes' + super(PowerItemType, self).buildChildren(child_, node, nodeName_, True) +# end class PowerItemType + + +class EnergyItemType(ItemBaseType): + """Base for the measurement of Energy""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(EnergyItemType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, EnergyItemType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if EnergyItemType.subclass: + return EnergyItemType.subclass(*args_, **kwargs_) + else: + return EnergyItemType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(EnergyItemType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='EnergyItemType', namespacedef_=' xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('EnergyItemType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='EnergyItemType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='EnergyItemType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='EnergyItemType'): + super(EnergyItemType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='EnergyItemType') + def exportChildren(self, outfile, level, namespace_='power:', name_='EnergyItemType', fromsubclass_=False, pretty_print=True): + super(EnergyItemType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(EnergyItemType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(EnergyItemType, self).buildChildren(child_, node, nodeName_, True) +# end class EnergyItemType + + +class VoltageType(ItemBaseType): + """Voltage""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(VoltageType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, VoltageType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if VoltageType.subclass: + return VoltageType.subclass(*args_, **kwargs_) + else: + return VoltageType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(VoltageType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='power:', name_='VoltageType', namespacedef_=' xmlns:power="http://docs.oasis-open.org/ns/emix/2011/06/power"', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('VoltageType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='VoltageType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='power:', name_='VoltageType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='power:', name_='VoltageType'): + super(VoltageType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='VoltageType') + def exportChildren(self, outfile, level, namespace_='power:', name_='VoltageType', fromsubclass_=False, pretty_print=True): + super(VoltageType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(VoltageType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(VoltageType, self).buildChildren(child_, node, nodeName_, True) +# end class VoltageType + + +class oadrReportPayloadType(ReportPayloadType): + """Report payload for use in reports.""" + subclass = None + superclass = ReportPayloadType + def __init__(self, rID=None, confidence=None, accuracy=None, payloadBase=None, oadrDataQuality=None): + self.original_tagname_ = None + super(oadrReportPayloadType, self).__init__(rID, confidence, accuracy, payloadBase, ) + self.oadrDataQuality = oadrDataQuality + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrReportPayloadType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrReportPayloadType.subclass: + return oadrReportPayloadType.subclass(*args_, **kwargs_) + else: + return oadrReportPayloadType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrDataQuality(self): return self.oadrDataQuality + def set_oadrDataQuality(self, oadrDataQuality): self.oadrDataQuality = oadrDataQuality + def hasContent_(self): + if ( + self.oadrDataQuality is not None or + super(oadrReportPayloadType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrReportPayloadType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrReportPayloadType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportPayloadType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrReportPayloadType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrReportPayloadType'): + super(oadrReportPayloadType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportPayloadType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrReportPayloadType', fromsubclass_=False, pretty_print=True): + super(oadrReportPayloadType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrDataQuality is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.oadrDataQuality), input_name='oadrDataQuality')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrReportPayloadType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrDataQuality': + oadrDataQuality_ = child_.text + oadrDataQuality_ = self.gds_validate_string(oadrDataQuality_, node, 'oadrDataQuality') + self.oadrDataQuality = oadrDataQuality_ + super(oadrReportPayloadType, self).buildChildren(child_, node, nodeName_, True) +# end class oadrReportPayloadType + + +class oadrGBStreamPayloadBase(StreamPayloadBaseType): + subclass = None + superclass = StreamPayloadBaseType + def __init__(self, feed=None): + self.original_tagname_ = None + super(oadrGBStreamPayloadBase, self).__init__() + self.feed = feed + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrGBStreamPayloadBase) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrGBStreamPayloadBase.subclass: + return oadrGBStreamPayloadBase.subclass(*args_, **kwargs_) + else: + return oadrGBStreamPayloadBase(*args_, **kwargs_) + factory = staticmethod(factory) + def get_feed(self): return self.feed + def set_feed(self, feed): self.feed = feed + def hasContent_(self): + if ( + self.feed is not None or + super(oadrGBStreamPayloadBase, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrGBStreamPayloadBase', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrGBStreamPayloadBase') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrGBStreamPayloadBase') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrGBStreamPayloadBase', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrGBStreamPayloadBase'): + super(oadrGBStreamPayloadBase, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrGBStreamPayloadBase') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrGBStreamPayloadBase', fromsubclass_=False, pretty_print=True): + super(oadrGBStreamPayloadBase, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.feed is not None: + self.feed.export(outfile, level, namespace_='atom:', name_='feed', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrGBStreamPayloadBase, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'feed': + obj_ = feedType.factory() + obj_.build(child_) + self.feed = obj_ + obj_.original_tagname_ = 'feed' + super(oadrGBStreamPayloadBase, self).buildChildren(child_, node, nodeName_, True) +# end class oadrGBStreamPayloadBase + + +class oadrGBItemBase(ItemBaseType): + subclass = None + superclass = ItemBaseType + def __init__(self, feed=None): + self.original_tagname_ = None + super(oadrGBItemBase, self).__init__() + self.feed = feed + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrGBItemBase) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrGBItemBase.subclass: + return oadrGBItemBase.subclass(*args_, **kwargs_) + else: + return oadrGBItemBase(*args_, **kwargs_) + factory = staticmethod(factory) + def get_feed(self): return self.feed + def set_feed(self, feed): self.feed = feed + def hasContent_(self): + if ( + self.feed is not None or + super(oadrGBItemBase, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrGBItemBase', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrGBItemBase') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrGBItemBase') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrGBItemBase', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrGBItemBase'): + super(oadrGBItemBase, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrGBItemBase') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrGBItemBase', fromsubclass_=False, pretty_print=True): + super(oadrGBItemBase, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.feed is not None: + self.feed.export(outfile, level, namespace_='atom:', name_='feed', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrGBItemBase, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'feed': + obj_ = feedType.factory() + obj_.build(child_) + self.feed = obj_ + obj_.original_tagname_ = 'feed' + super(oadrGBItemBase, self).buildChildren(child_, node, nodeName_, True) +# end class oadrGBItemBase + + +class oadrPayloadResourceStatusType(PayloadBaseType): + """This is the payload for reports that require a status.""" + subclass = None + superclass = PayloadBaseType + def __init__(self, oadrOnline=None, oadrManualOverride=None, oadrLoadControlState=None): + self.original_tagname_ = None + super(oadrPayloadResourceStatusType, self).__init__() + self.oadrOnline = oadrOnline + self.oadrManualOverride = oadrManualOverride + self.oadrLoadControlState = oadrLoadControlState + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrPayloadResourceStatusType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrPayloadResourceStatusType.subclass: + return oadrPayloadResourceStatusType.subclass(*args_, **kwargs_) + else: + return oadrPayloadResourceStatusType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_oadrOnline(self): return self.oadrOnline + def set_oadrOnline(self, oadrOnline): self.oadrOnline = oadrOnline + def get_oadrManualOverride(self): return self.oadrManualOverride + def set_oadrManualOverride(self, oadrManualOverride): self.oadrManualOverride = oadrManualOverride + def get_oadrLoadControlState(self): return self.oadrLoadControlState + def set_oadrLoadControlState(self, oadrLoadControlState): self.oadrLoadControlState = oadrLoadControlState + def hasContent_(self): + if ( + self.oadrOnline is not None or + self.oadrManualOverride is not None or + self.oadrLoadControlState is not None or + super(oadrPayloadResourceStatusType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrPayloadResourceStatusType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrPayloadResourceStatusType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrPayloadResourceStatusType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrPayloadResourceStatusType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrPayloadResourceStatusType'): + super(oadrPayloadResourceStatusType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrPayloadResourceStatusType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrPayloadResourceStatusType', fromsubclass_=False, pretty_print=True): + super(oadrPayloadResourceStatusType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.oadrOnline is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrOnline, input_name='oadrOnline'), eol_)) + if self.oadrManualOverride is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_boolean(self.oadrManualOverride, input_name='oadrManualOverride'), eol_)) + if self.oadrLoadControlState is not None: + self.oadrLoadControlState.export(outfile, level, namespace_='oadr:', name_='oadrLoadControlState', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrPayloadResourceStatusType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'oadrOnline': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrOnline') + self.oadrOnline = ival_ + elif nodeName_ == 'oadrManualOverride': + sval_ = child_.text + if sval_ in ('true', '1'): + ival_ = True + elif sval_ in ('false', '0'): + ival_ = False + else: + raise_parse_error(child_, 'requires boolean') + ival_ = self.gds_validate_boolean(ival_, node, 'oadrManualOverride') + self.oadrManualOverride = ival_ + elif nodeName_ == 'oadrLoadControlState': + obj_ = oadrLoadControlStateType.factory() + obj_.build(child_) + self.oadrLoadControlState = obj_ + obj_.original_tagname_ = 'oadrLoadControlState' + super(oadrPayloadResourceStatusType, self).buildChildren(child_, node, nodeName_, True) +# end class oadrPayloadResourceStatusType + + +class oadrReportType(StreamBaseType): + """eiReport is a Stream of [measurements] recorded over time and + delivered to the requestor periodically. The readings may be + actual, computed, summed if derived in some other manner.""" + subclass = None + superclass = StreamBaseType + def __init__(self, dtstart=None, duration=None, intervals=None, eiReportID=None, oadrReportDescription=None, reportRequestID=None, reportSpecifierID=None, reportName=None, createdDateTime=None): + self.original_tagname_ = None + super(oadrReportType, self).__init__(dtstart, duration, intervals, ) + self.eiReportID = eiReportID + if oadrReportDescription is None: + self.oadrReportDescription = [] + else: + self.oadrReportDescription = oadrReportDescription + self.reportRequestID = reportRequestID + self.reportSpecifierID = reportSpecifierID + self.reportName = reportName + if isinstance(createdDateTime, BaseStrType_): + initvalue_ = datetime_.datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S') + else: + initvalue_ = createdDateTime + self.createdDateTime = initvalue_ + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrReportType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrReportType.subclass: + return oadrReportType.subclass(*args_, **kwargs_) + else: + return oadrReportType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_eiReportID(self): return self.eiReportID + def set_eiReportID(self, eiReportID): self.eiReportID = eiReportID + def get_oadrReportDescription(self): return self.oadrReportDescription + def set_oadrReportDescription(self, oadrReportDescription): self.oadrReportDescription = oadrReportDescription + def add_oadrReportDescription(self, value): self.oadrReportDescription.append(value) + def insert_oadrReportDescription_at(self, index, value): self.oadrReportDescription.insert(index, value) + def replace_oadrReportDescription_at(self, index, value): self.oadrReportDescription[index] = value + def get_reportRequestID(self): return self.reportRequestID + def set_reportRequestID(self, reportRequestID): self.reportRequestID = reportRequestID + def get_reportSpecifierID(self): return self.reportSpecifierID + def set_reportSpecifierID(self, reportSpecifierID): self.reportSpecifierID = reportSpecifierID + def get_reportName(self): return self.reportName + def set_reportName(self, reportName): self.reportName = reportName + def get_createdDateTime(self): return self.createdDateTime + def set_createdDateTime(self, createdDateTime): self.createdDateTime = createdDateTime + def hasContent_(self): + if ( + self.eiReportID is not None or + self.oadrReportDescription or + self.reportRequestID is not None or + self.reportSpecifierID is not None or + self.reportName is not None or + self.createdDateTime is not None or + super(oadrReportType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrReportType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrReportType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrReportType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrReportType'): + super(oadrReportType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrReportType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrReportType', fromsubclass_=False, pretty_print=True): + super(oadrReportType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.eiReportID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.eiReportID), input_name='eiReportID')), eol_)) + for oadrReportDescription_ in self.oadrReportDescription: + oadrReportDescription_.export(outfile, level, namespace_='oadr:', name_='oadrReportDescription', pretty_print=pretty_print) + if self.reportRequestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportRequestID), input_name='reportRequestID')), eol_)) + if self.reportSpecifierID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportSpecifierID), input_name='reportSpecifierID')), eol_)) + if self.reportName is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.reportName), input_name='reportName')), eol_)) + if self.createdDateTime is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_datetime(self.createdDateTime, input_name='createdDateTime'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrReportType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'eiReportID': + eiReportID_ = child_.text + eiReportID_ = self.gds_validate_string(eiReportID_, node, 'eiReportID') + self.eiReportID = eiReportID_ + elif nodeName_ == 'oadrReportDescription': + obj_ = oadrReportDescriptionType.factory() + obj_.build(child_) + self.oadrReportDescription.append(obj_) + obj_.original_tagname_ = 'oadrReportDescription' + elif nodeName_ == 'reportRequestID': + reportRequestID_ = child_.text + reportRequestID_ = self.gds_validate_string(reportRequestID_, node, 'reportRequestID') + self.reportRequestID = reportRequestID_ + elif nodeName_ == 'reportSpecifierID': + reportSpecifierID_ = child_.text + reportSpecifierID_ = self.gds_validate_string(reportSpecifierID_, node, 'reportSpecifierID') + self.reportSpecifierID = reportSpecifierID_ + elif nodeName_ == 'reportName': + reportName_ = child_.text + reportName_ = self.gds_validate_string(reportName_, node, 'reportName') + self.reportName = reportName_ + elif nodeName_ == 'createdDateTime': + sval_ = child_.text + dval_ = self.gds_parse_datetime(sval_) + self.createdDateTime = dval_ + super(oadrReportType, self).buildChildren(child_, node, nodeName_, True) +# end class oadrReportType + + +class pulseCountType(ItemBaseType): + """Pulse Count""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, pulseFactor=None): + self.original_tagname_ = None + super(pulseCountType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.pulseFactor = pulseFactor + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, pulseCountType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if pulseCountType.subclass: + return pulseCountType.subclass(*args_, **kwargs_) + else: + return pulseCountType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_pulseFactor(self): return self.pulseFactor + def set_pulseFactor(self, pulseFactor): self.pulseFactor = pulseFactor + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.pulseFactor is not None or + super(pulseCountType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='pulseCountType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('pulseCountType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='pulseCountType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='pulseCountType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='pulseCountType'): + super(pulseCountType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='pulseCountType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='pulseCountType', fromsubclass_=False, pretty_print=True): + super(pulseCountType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.pulseFactor is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_format_float(self.pulseFactor, input_name='pulseFactor'), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(pulseCountType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'pulseFactor': + sval_ = child_.text + try: + fval_ = float(sval_) + except (TypeError, ValueError) as exp: + raise_parse_error(child_, 'requires float or double: %s' % exp) + fval_ = self.gds_validate_float(fval_, node, 'pulseFactor') + self.pulseFactor = fval_ + super(pulseCountType, self).buildChildren(child_, node, nodeName_, True) +# end class pulseCountType + + +class temperatureType(ItemBaseType): + """temperature""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(temperatureType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.validate_temperatureUnitType(self.itemUnits) + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, temperatureType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if temperatureType.subclass: + return temperatureType.subclass(*args_, **kwargs_) + else: + return temperatureType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def validate_temperatureUnitType(self, value): + # Validate type temperatureUnitType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['celsius', 'fahrenheit'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on temperatureUnitType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(temperatureType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='temperatureType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('temperatureType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='temperatureType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='temperatureType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='temperatureType'): + super(temperatureType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='temperatureType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='temperatureType', fromsubclass_=False, pretty_print=True): + super(temperatureType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(temperatureType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + if itemUnits_: + itemUnits_ = re_.sub(String_cleanup_pat_, " ", itemUnits_).strip() + else: + itemUnits_ = "" + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + # validate type temperatureUnitType + self.validate_temperatureUnitType(self.itemUnits) + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(temperatureType, self).buildChildren(child_, node, nodeName_, True) +# end class temperatureType + + +class ThermType(ItemBaseType): + """Therm""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(ThermType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, ThermType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if ThermType.subclass: + return ThermType.subclass(*args_, **kwargs_) + else: + return ThermType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(ThermType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='ThermType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('ThermType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='ThermType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='ThermType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='ThermType'): + super(ThermType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='ThermType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='ThermType', fromsubclass_=False, pretty_print=True): + super(ThermType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(ThermType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(ThermType, self).buildChildren(child_, node, nodeName_, True) +# end class ThermType + + +class FrequencyType(ItemBaseType): + """Frequency""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(FrequencyType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, FrequencyType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if FrequencyType.subclass: + return FrequencyType.subclass(*args_, **kwargs_) + else: + return FrequencyType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(FrequencyType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='FrequencyType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('FrequencyType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='FrequencyType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='FrequencyType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='FrequencyType'): + super(FrequencyType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='FrequencyType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='FrequencyType', fromsubclass_=False, pretty_print=True): + super(FrequencyType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(FrequencyType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(FrequencyType, self).buildChildren(child_, node, nodeName_, True) +# end class FrequencyType + + +class currencyType(ItemBaseType): + """currency""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(currencyType, self).__init__() + self.itemDescription = itemDescription + self.validate_currencyItemDescriptionType(self.itemDescription) + self.itemUnits = itemUnits + self.validate_ISO3AlphaCurrencyCodeContentType(self.itemUnits) + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, currencyType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if currencyType.subclass: + return currencyType.subclass(*args_, **kwargs_) + else: + return currencyType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def validate_currencyItemDescriptionType(self, value): + # Validate type currencyItemDescriptionType, a restriction on xs:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['currency', 'currencyPerKW', 'currencyPerKWh'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on currencyItemDescriptionType' % {"value" : value.encode("utf-8")} ) + def validate_ISO3AlphaCurrencyCodeContentType(self, value): + # Validate type ISO3AlphaCurrencyCodeContentType, a restriction on xsd:token. + if value is not None and Validate_simpletypes_: + value = str(value) + enumerations = ['AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BOV', 'BRL', 'BSD', 'BTN', 'BWP', 'BYR', 'BZD', 'CAD', 'CDF', 'CHE', 'CHF', 'CHW', 'CLF', 'CLP', 'CNY', 'COP', 'COU', 'CRC', 'CUC', 'CUP', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EEK', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GWP', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'IQD', 'IRR', 'ISK', 'JMD', 'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', 'LVL', 'LYD', 'MAD', 'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', 'MXV', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'OMR', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS', 'SRD', 'STD', 'SVC', 'SYP', 'SZL', 'THB', 'TJS', 'TMT', 'TND', 'TOP', 'TRY', 'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'USD', 'USN', 'USS', 'UYI', 'UYU', 'UZS', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XAG', 'XAU', 'XBA', 'XBB', 'XBC', 'XBD', 'XCD', 'XDR', 'XFU', 'XOF', 'XPD', 'XPF', 'XPF', 'XPF', 'XPT', 'XTS', 'XXX', 'YER', 'ZAR', 'ZMK', 'ZWL'] + enumeration_respectee = False + for enum in enumerations: + if value == enum: + enumeration_respectee = True + break + if not enumeration_respectee: + warnings_.warn('Value "%(value)s" does not match xsd enumeration restriction on ISO3AlphaCurrencyCodeContentType' % {"value" : value.encode("utf-8")} ) + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(currencyType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='currencyType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('currencyType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='currencyType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='currencyType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='currencyType'): + super(currencyType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='currencyType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='currencyType', fromsubclass_=False, pretty_print=True): + super(currencyType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(currencyType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + if itemDescription_: + itemDescription_ = re_.sub(String_cleanup_pat_, " ", itemDescription_).strip() + else: + itemDescription_ = "" + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + # validate type currencyItemDescriptionType + self.validate_currencyItemDescriptionType(self.itemDescription) + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + if itemUnits_: + itemUnits_ = re_.sub(String_cleanup_pat_, " ", itemUnits_).strip() + else: + itemUnits_ = "" + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + # validate type ISO3AlphaCurrencyCodeContentType + self.validate_ISO3AlphaCurrencyCodeContentType(self.itemUnits) + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(currencyType, self).buildChildren(child_, node, nodeName_, True) +# end class currencyType + + +class CurrentType(ItemBaseType): + """Current""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(CurrentType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, CurrentType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if CurrentType.subclass: + return CurrentType.subclass(*args_, **kwargs_) + else: + return CurrentType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(CurrentType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='CurrentType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('CurrentType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='CurrentType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='CurrentType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='CurrentType'): + super(CurrentType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='CurrentType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='CurrentType', fromsubclass_=False, pretty_print=True): + super(CurrentType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(CurrentType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(CurrentType, self).buildChildren(child_, node, nodeName_, True) +# end class CurrentType + + +class BaseUnitType(ItemBaseType): + """Custom Units""" + subclass = None + superclass = ItemBaseType + def __init__(self, itemDescription=None, itemUnits=None, siScaleCode=None): + self.original_tagname_ = None + super(BaseUnitType, self).__init__() + self.itemDescription = itemDescription + self.itemUnits = itemUnits + self.siScaleCode = siScaleCode + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, BaseUnitType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if BaseUnitType.subclass: + return BaseUnitType.subclass(*args_, **kwargs_) + else: + return BaseUnitType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_itemDescription(self): return self.itemDescription + def set_itemDescription(self, itemDescription): self.itemDescription = itemDescription + def get_itemUnits(self): return self.itemUnits + def set_itemUnits(self, itemUnits): self.itemUnits = itemUnits + def get_siScaleCode(self): return self.siScaleCode + def set_siScaleCode(self, siScaleCode): self.siScaleCode = siScaleCode + def hasContent_(self): + if ( + self.itemDescription is not None or + self.itemUnits is not None or + self.siScaleCode is not None or + super(BaseUnitType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='BaseUnitType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('BaseUnitType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='BaseUnitType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='BaseUnitType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='BaseUnitType'): + super(BaseUnitType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='BaseUnitType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='BaseUnitType', fromsubclass_=False, pretty_print=True): + super(BaseUnitType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.itemDescription is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemDescription), input_name='itemDescription')), eol_)) + if self.itemUnits is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.itemUnits), input_name='itemUnits')), eol_)) + if self.siScaleCode is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.siScaleCode), input_name='siScaleCode')), eol_)) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(BaseUnitType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'itemDescription': + itemDescription_ = child_.text + itemDescription_ = self.gds_validate_string(itemDescription_, node, 'itemDescription') + self.itemDescription = itemDescription_ + elif nodeName_ == 'itemUnits': + itemUnits_ = child_.text + itemUnits_ = self.gds_validate_string(itemUnits_, node, 'itemUnits') + self.itemUnits = itemUnits_ + elif nodeName_ == 'siScaleCode': + siScaleCode_ = child_.text + siScaleCode_ = self.gds_validate_string(siScaleCode_, node, 'siScaleCode') + self.siScaleCode = siScaleCode_ + super(BaseUnitType, self).buildChildren(child_, node, nodeName_, True) +# end class BaseUnitType + + +class oadrCreateOptType(EiOptType): + subclass = None + superclass = EiOptType + def __init__(self, schemaVersion=None, optID=None, optType=None, optReason=None, marketContext=None, venID=None, vavailability=None, createdDateTime=None, requestID=None, qualifiedEventID=None, eiTarget=None, oadrDeviceClass=None): + self.original_tagname_ = None + super(oadrCreateOptType, self).__init__(schemaVersion, optID, optType, optReason, marketContext, venID, vavailability, createdDateTime, ) + self.requestID = requestID + self.qualifiedEventID = qualifiedEventID + self.eiTarget = eiTarget + self.oadrDeviceClass = oadrDeviceClass + def factory(*args_, **kwargs_): + if CurrentSubclassModule_ is not None: + subclass = getSubclassFromModule_( + CurrentSubclassModule_, oadrCreateOptType) + if subclass is not None: + return subclass(*args_, **kwargs_) + if oadrCreateOptType.subclass: + return oadrCreateOptType.subclass(*args_, **kwargs_) + else: + return oadrCreateOptType(*args_, **kwargs_) + factory = staticmethod(factory) + def get_requestID(self): return self.requestID + def set_requestID(self, requestID): self.requestID = requestID + def get_qualifiedEventID(self): return self.qualifiedEventID + def set_qualifiedEventID(self, qualifiedEventID): self.qualifiedEventID = qualifiedEventID + def get_eiTarget(self): return self.eiTarget + def set_eiTarget(self, eiTarget): self.eiTarget = eiTarget + def get_oadrDeviceClass(self): return self.oadrDeviceClass + def set_oadrDeviceClass(self, oadrDeviceClass): self.oadrDeviceClass = oadrDeviceClass + def hasContent_(self): + if ( + self.requestID is not None or + self.qualifiedEventID is not None or + self.eiTarget is not None or + self.oadrDeviceClass is not None or + super(oadrCreateOptType, self).hasContent_() + ): + return True + else: + return False + def export(self, outfile, level, namespace_='oadr:', name_='oadrCreateOptType', namespacedef_='', pretty_print=True): + imported_ns_def_ = GenerateDSNamespaceDefs_.get('oadrCreateOptType') + if imported_ns_def_ is not None: + namespacedef_ = imported_ns_def_ + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.original_tagname_ is not None: + name_ = self.original_tagname_ + showIndent(outfile, level, pretty_print) + outfile.write('<%s%s%s' % (namespace_, name_, namespacedef_ and ' ' + namespacedef_ or '', )) + already_processed = set() + self.exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreateOptType') + if self.hasContent_(): + outfile.write('>%s' % (eol_, )) + self.exportChildren(outfile, level + 1, namespace_='oadr:', name_='oadrCreateOptType', pretty_print=pretty_print) + showIndent(outfile, level, pretty_print) + outfile.write('%s' % (namespace_, name_, eol_)) + else: + outfile.write('/>%s' % (eol_, )) + def exportAttributes(self, outfile, level, already_processed, namespace_='oadr:', name_='oadrCreateOptType'): + super(oadrCreateOptType, self).exportAttributes(outfile, level, already_processed, namespace_, name_='oadrCreateOptType') + def exportChildren(self, outfile, level, namespace_='oadr:', name_='oadrCreateOptType', fromsubclass_=False, pretty_print=True): + super(oadrCreateOptType, self).exportChildren(outfile, level, namespace_, name_, True, pretty_print=pretty_print) + if pretty_print: + eol_ = '\n' + else: + eol_ = '' + if self.requestID is not None: + showIndent(outfile, level, pretty_print) + outfile.write('%s%s' % (self.gds_encode(self.gds_format_string(quote_xml(self.requestID), input_name='requestID')), eol_)) + if self.qualifiedEventID is not None: + self.qualifiedEventID.export(outfile, level, namespace_='ei:', name_='qualifiedEventID', pretty_print=pretty_print) + if self.eiTarget is not None: + self.eiTarget.export(outfile, level, namespace_='ei:', name_='eiTarget', pretty_print=pretty_print) + if self.oadrDeviceClass is not None: + self.oadrDeviceClass.export(outfile, level, namespace_='oadr:', name_='oadrDeviceClass', pretty_print=pretty_print) + def build(self, node): + already_processed = set() + self.buildAttributes(node, node.attrib, already_processed) + for child in node: + nodeName_ = Tag_pattern_.match(child.tag).groups()[-1] + self.buildChildren(child, node, nodeName_) + return self + def buildAttributes(self, node, attrs, already_processed): + super(oadrCreateOptType, self).buildAttributes(node, attrs, already_processed) + def buildChildren(self, child_, node, nodeName_, fromsubclass_=False): + if nodeName_ == 'requestID': + requestID_ = child_.text + requestID_ = self.gds_validate_string(requestID_, node, 'requestID') + self.requestID = requestID_ + elif nodeName_ == 'qualifiedEventID': + obj_ = QualifiedEventIDType.factory() + obj_.build(child_) + self.qualifiedEventID = obj_ + obj_.original_tagname_ = 'qualifiedEventID' + elif nodeName_ == 'eiTarget': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.eiTarget = obj_ + obj_.original_tagname_ = 'eiTarget' + elif nodeName_ == 'oadrDeviceClass': + obj_ = EiTargetType.factory() + obj_.build(child_) + self.oadrDeviceClass = obj_ + obj_.original_tagname_ = 'oadrDeviceClass' + super(oadrCreateOptType, self).buildChildren(child_, node, nodeName_, True) +# end class oadrCreateOptType + + +GDSClassesMapping = { + 'BatchItemInfo': BatchItemInfo, + 'CanonicalizationMethod': CanonicalizationMethodType, + 'DEREncodedKeyValue': DEREncodedKeyValueType, + 'DSAKeyValue': DSAKeyValueType, + 'DateTimeInterval': DateTimeInterval, + 'DigestMethod': DigestMethodType, + 'ECKeyValue': ECKeyValueType, + 'ElectricPowerQualitySummary': ElectricPowerQualitySummary, + 'ElectricPowerUsageSummary': ElectricPowerUsageSummary, + 'GnB': CharTwoFieldParamsType, + 'IdentifiedObject': IdentifiedObject, + 'IntervalBlock': IntervalBlock, + 'IntervalReading': IntervalReading, + 'KeyInfo': KeyInfoType, + 'KeyInfoReference': KeyInfoReferenceType, + 'KeyValue': KeyValueType, + 'LocalTimeParameters': TimeConfiguration, + 'Manifest': ManifestType, + 'MeterReading': MeterReading, + 'Object': Object, + 'Object': ObjectType, + 'PGPData': PGPDataType, + 'PnB': PnBFieldParamsType, + 'Prime': PrimeFieldParamsType, + 'RSAKeyValue': RSAKeyValueType, + 'ReadingQuality': ReadingQuality, + 'ReadingType': ReadingType, + 'Reference': ReferenceType, + 'RetrievalMethod': RetrievalMethodType, + 'SPKIData': SPKIDataType, + 'ServiceStatus': ServiceStatus, + 'Signature': SignatureType, + 'SignatureMethod': SignatureMethodType, + 'SignatureProperties': SignaturePropertiesType, + 'SignatureProperty': SignaturePropertyType, + 'SignatureValue': SignatureValueType, + 'SignedInfo': SignedInfoType, + 'SummaryMeasurement': SummaryMeasurement, + 'Therm': ThermType, + 'TnB': TnBFieldParamsType, + 'Transform': TransformType, + 'Transforms': TransformsType, + 'UsagePoint': UsagePoint, + 'X509Data': X509DataType, + 'X509Digest': X509DigestType, + 'aggregatedPnode': AggregatedPnodeType, + 'available': AvailableType, + 'currency': currencyType, + 'currencyPerKW': currencyType, + 'currencyPerKWh': currencyType, + 'currencyPerThm': currencyType, + 'current': CurrentType, + 'currentValue': currentValueType, + 'customUnit': BaseUnitType, + 'duration': DurationPropType, + 'eiActivePeriod': eiActivePeriodType, + 'eiEvent': eiEventType, + 'eiEventBaseline': eiEventBaselineType, + 'eiEventSignal': eiEventSignalType, + 'eiEventSignals': eiEventSignalsType, + 'eiResponse': EiResponseType, + 'eiTarget': EiTargetType, + 'endDeviceAsset': EndDeviceAssetType, + 'energyApparent': EnergyApparentType, + 'energyItem': EnergyItemType, + 'energyReactive': EnergyReactiveType, + 'energyReal': EnergyRealType, + 'entry': entryType, + 'eventDescriptor': eventDescriptorType, + 'feed': feedType, + 'frequency': FrequencyType, + 'granularity': DurationPropType, + 'interval': IntervalType, + 'interval': WsCalendarIntervalType, + 'itemBase': ItemBaseType, + 'meterAsset': MeterAssetType, + 'oadrCancelOpt': oadrCancelOptType, + 'oadrCancelPartyRegistration': oadrCancelPartyRegistrationType, + 'oadrCancelReport': oadrCancelReportType, + 'oadrCanceledOpt': oadrCanceledOptType, + 'oadrCanceledPartyRegistration': oadrCanceledPartyRegistrationType, + 'oadrCanceledReport': oadrCanceledReportType, + 'oadrCreateOpt': oadrCreateOptType, + 'oadrCreatePartyRegistration': oadrCreatePartyRegistrationType, + 'oadrCreateReport': oadrCreateReportType, + 'oadrCreatedEvent': oadrCreatedEventType, + 'oadrCreatedOpt': oadrCreatedOptType, + 'oadrCreatedPartyRegistration': oadrCreatedPartyRegistrationType, + 'oadrCreatedReport': oadrCreatedReportType, + 'oadrDeviceClass': EiTargetType, + 'oadrDistributeEvent': oadrDistributeEventType, + 'oadrGBDataDescription': oadrGBItemBase, + 'oadrGBPayload': oadrGBStreamPayloadBase, + 'oadrLoadControlState': oadrLoadControlStateType, + 'oadrPayloadResourceStatus': oadrPayloadResourceStatusType, + 'oadrPendingReports': oadrPendingReportsType, + 'oadrPoll': oadrPollType, + 'oadrQueryRegistration': oadrQueryRegistrationType, + 'oadrRegisterReport': oadrRegisterReportType, + 'oadrRegisteredReport': oadrRegisteredReportType, + 'oadrReport': oadrReportType, + 'oadrReportDescription': oadrReportDescriptionType, + 'oadrReportPayload': oadrReportPayloadType, + 'oadrReportRequest': oadrReportRequestType, + 'oadrRequestEvent': oadrRequestEventType, + 'oadrRequestReregistration': oadrRequestReregistrationType, + 'oadrRequestedOadrPollFreq': DurationPropType, + 'oadrResponse': oadrResponseType, + 'oadrSamplingRate': oadrSamplingRateType, + 'oadrUpdateReport': oadrUpdateReportType, + 'oadrUpdatedReport': oadrUpdatedReportType, + 'payloadBase': PayloadBaseType, + 'payloadFloat': PayloadFloatType, + 'pnode': PnodeType, + 'powerApparent': PowerApparentType, + 'powerAttributes': PowerAttributesType, + 'powerItem': PowerItemType, + 'powerReactive': PowerReactiveType, + 'powerReal': PowerRealType, + 'pulseCount': pulseCountType, + 'qualifiedEventID': QualifiedEventIDType, + 'reportDataSource': EiTargetType, + 'reportSpecifier': ReportSpecifierType, + 'reportSubject': EiTargetType, + 'responses': ArrayofResponses, + 'serviceArea': ServiceAreaType, + 'serviceDeliveryPoint': ServiceDeliveryPointType, + 'serviceLocation': ServiceLocationType, + 'signalPayload': signalPayloadType, + 'specifierPayload': SpecifierPayloadType, + 'streamBase': StreamBaseType, + 'streamPayloadBase': StreamPayloadBaseType, + 'temperature': temperatureType, + 'transportInterface': TransportInterfaceType, + 'vavailability': VavailabilityType, + 'voltage': VoltageType, + 'x-eiNotification': DurationPropType, + 'x-eiRampUp': DurationPropType, + 'x-eiRecovery': DurationPropType, +} + + +USAGE_TEXT = """ +Usage: python .py [ -s ] +""" + + +def usage(): + print(USAGE_TEXT) + sys.exit(1) + + +def get_root_tag(node): + tag = Tag_pattern_.match(node.tag).groups()[-1] + rootClass = GDSClassesMapping.get(tag) + if rootClass is None: + rootClass = globals().get(tag) + return tag, rootClass + + +def parse(inFileName, silence=False): + parser = None + doc = parsexml_(inFileName, parser) + rootNode = doc.getroot() + rootTag, rootClass = get_root_tag(rootNode) + if rootClass is None: + rootTag = 'oadrPayload' + rootClass = oadrPayload + rootObj = rootClass.factory() + rootObj.build(rootNode) + # Enable Python to collect the space used by the DOM. + doc = None + if not silence: + sys.stdout.write('\n') + rootObj.export( + sys.stdout, 0, name_=rootTag, + namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"', + pretty_print=True) + return rootObj + + +def parseEtree(inFileName, silence=False): + parser = None + doc = parsexml_(inFileName, parser) + rootNode = doc.getroot() + rootTag, rootClass = get_root_tag(rootNode) + if rootClass is None: + rootTag = 'oadrPayload' + rootClass = oadrPayload + rootObj = rootClass.factory() + rootObj.build(rootNode) + # Enable Python to collect the space used by the DOM. + doc = None + mapping = {} + rootElement = rootObj.to_etree(None, name_=rootTag, mapping_=mapping) + reverse_mapping = rootObj.gds_reverse_node_mapping(mapping) + if not silence: + content = etree_.tostring( + rootElement, pretty_print=True, + xml_declaration=True, encoding="utf-8") + sys.stdout.write(content) + sys.stdout.write('\n') + return rootObj, rootElement, mapping, reverse_mapping + + +def parseString(inString, silence=False): + '''Parse a string, create the object tree, and export it. + + Arguments: + - inString -- A string. This XML fragment should not start + with an XML declaration containing an encoding. + - silence -- A boolean. If False, export the object. + Returns -- The root object in the tree. + ''' + parser = None + rootNode= parsexmlstring_(inString, parser) + rootTag, rootClass = get_root_tag(rootNode) + if rootClass is None: + rootTag = 'oadrPayload' + rootClass = oadrPayload + rootObj = rootClass.factory() + rootObj.build(rootNode) + # Enable Python to collect the space used by the DOM. + if not silence: + sys.stdout.write('\n') + rootObj.export( + sys.stdout, 0, name_=rootTag, + namespacedef_='xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07"') + return rootObj + + +def parseLiteral(inFileName, silence=False): + parser = None + doc = parsexml_(inFileName, parser) + rootNode = doc.getroot() + rootTag, rootClass = get_root_tag(rootNode) + if rootClass is None: + rootTag = 'oadrPayload' + rootClass = oadrPayload + rootObj = rootClass.factory() + rootObj.build(rootNode) + # Enable Python to collect the space used by the DOM. + doc = None + if not silence: + sys.stdout.write('#from oadr_20b import *\n\n') + sys.stdout.write('import oadr_20b as model_\n\n') + sys.stdout.write('rootObj = model_.rootClass(\n') + rootObj.exportLiteral(sys.stdout, 0, name_=rootTag) + sys.stdout.write(')\n') + return rootObj + + +def main(): + args = sys.argv[1:] + if len(args) == 1: + parse(args[0]) + else: + usage() + + +if __name__ == '__main__': + #import pdb; pdb.set_trace() + main() + + +__all__ = [ + "AggregatedPnodeType", + "ArrayOfVavailabilityContainedComponents", + "ArrayofResponses", + "AvailableType", + "BaseUnitType", + "BatchItemInfo", + "CanonicalizationMethodType", + "CharTwoFieldParamsType", + "CurrentType", + "CurveType", + "DEREncodedKeyValueType", + "DSAKeyValueType", + "DateTimeInterval", + "DigestMethodType", + "DurationPropType", + "ECKeyValueType", + "ECParametersType", + "ECValidationDataType", + "EiOptType", + "EiResponseType", + "EiTargetType", + "ElectricPowerQualitySummary", + "ElectricPowerUsageSummary", + "EndDeviceAssetType", + "EnergyApparentType", + "EnergyItemType", + "EnergyReactiveType", + "EnergyRealType", + "FeatureCollection", + "FieldIDType", + "FrequencyType", + "IdentifiedObject", + "IntervalBlock", + "IntervalReading", + "IntervalType", + "ItemBaseType", + "KeyInfoReferenceType", + "KeyInfoType", + "KeyValueType", + "LineItem", + "LinearRingType", + "ManifestType", + "MeterAssetType", + "MeterReading", + "NamedCurveType", + "Object", + "ObjectType", + "PGPDataType", + "PayloadBaseType", + "PayloadFloatType", + "PnBFieldParamsType", + "PnodeType", + "PolygonType", + "PowerApparentType", + "PowerAttributesType", + "PowerItemType", + "PowerReactiveType", + "PowerRealType", + "PrimeFieldParamsType", + "QualifiedEventIDType", + "RSAKeyValueType", + "RationalNumber", + "ReadingInterharmonic", + "ReadingQuality", + "ReadingType", + "ReferenceType", + "ReportPayloadType", + "ReportSpecifierType", + "RetrievalMethodType", + "SPKIDataType", + "ServiceAreaType", + "ServiceCategory", + "ServiceDeliveryPoint", + "ServiceDeliveryPointType", + "ServiceLocationType", + "ServiceStatus", + "SignatureMethodType", + "SignaturePropertiesType", + "SignaturePropertyType", + "SignatureType", + "SignatureValueType", + "SignedInfoType", + "SpecifierPayloadType", + "StreamBaseType", + "StreamPayloadBaseType", + "SummaryMeasurement", + "ThermType", + "TimeConfiguration", + "TnBFieldParamsType", + "TransformType", + "TransformsType", + "TransportInterfaceType", + "UsagePoint", + "VavailabilityType", + "VoltageType", + "WsCalendarIntervalType", + "X509DataType", + "X509DigestType", + "X509IssuerSerialType", + "categoryType", + "components", + "contentType", + "currencyType", + "currentValueType", + "dateTimeType", + "dtend", + "dtstart", + "eiActivePeriodType", + "eiCreatedEvent", + "eiEventBaselineType", + "eiEventSignalType", + "eiEventSignalsType", + "eiEventType", + "eiMarketContextType", + "eiRequestEvent", + "entryType", + "eventDescriptorType", + "eventResponseType", + "eventResponses", + "exteriorType", + "feedType", + "generatorType", + "iconType", + "idType", + "intervals", + "linkType", + "locationType", + "logoType", + "oadrCancelOptType", + "oadrCancelPartyRegistrationType", + "oadrCancelReportType", + "oadrCanceledOptType", + "oadrCanceledPartyRegistrationType", + "oadrCanceledReportType", + "oadrCreateOptType", + "oadrCreatePartyRegistrationType", + "oadrCreateReportType", + "oadrCreatedEventType", + "oadrCreatedOptType", + "oadrCreatedPartyRegistrationType", + "oadrCreatedReportType", + "oadrDistributeEventType", + "oadrEventType", + "oadrExtensionType", + "oadrExtensionsType", + "oadrGBItemBase", + "oadrGBStreamPayloadBase", + "oadrInfo", + "oadrLoadControlStateType", + "oadrLoadControlStateTypeType", + "oadrPayload", + "oadrPayloadResourceStatusType", + "oadrPendingReportsType", + "oadrPollType", + "oadrProfileType1", + "oadrProfiles", + "oadrQueryRegistrationType", + "oadrRegisterReportType", + "oadrRegisteredReportType", + "oadrReportDescriptionType", + "oadrReportPayloadType", + "oadrReportRequestType", + "oadrReportType", + "oadrRequestEventType", + "oadrRequestReregistrationType", + "oadrResponseType", + "oadrSamplingRateType", + "oadrServiceSpecificInfo", + "oadrServiceType", + "oadrSignedObject", + "oadrTransportType2", + "oadrTransports", + "oadrUpdateReportType", + "oadrUpdatedReportType", + "personType", + "properties", + "pulseCountType", + "refID", + "registrationID", + "signalPayloadType", + "sourceType", + "temperatureType", + "textType", + "toleranceType", + "tolerateType", + "uriType" +] diff --git a/services/unsupported/OpenADRVenAgent/openadrven/oadr_builder.py b/services/unsupported/OpenADRVenAgent/openadrven/oadr_builder.py new file mode 100644 index 0000000000..435f75560a --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/oadr_builder.py @@ -0,0 +1,389 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from datetime import datetime as dt +from datetime import timedelta +import isodate +import logging +import uuid + +from volttron.platform.agent import utils +from volttron.platform import jsonapi + +from . import oadr_20b +from .oadr_common import * + +utils.setup_logging() +_log = logging.getLogger(__name__) + + +class OadrBuilder(object): + """Abstract superclass. Build oadr model objects to send to the VTN.""" + + def __init__(self, request_id=None, ven_id=None, **kwargs): + self.request_id = request_id + self.ven_id = ven_id + + @staticmethod + def create_request_id(): + return uuid.uuid1() + + +class OadrPollBuilder(OadrBuilder): + + def __init__(self, **kwargs): + super(OadrPollBuilder, self).__init__(**kwargs) + + def build(self): + return oadr_20b.oadrPollType(schemaVersion=SCHEMA_VERSION, venID=self.ven_id) + + +class OadrQueryRegistrationBuilder(OadrBuilder): + + def __init__(self, **kwargs): + super(OadrQueryRegistrationBuilder, self).__init__(**kwargs) + + def build(self): + return oadr_20b.oadrQueryRegistrationType(schemaVersion=SCHEMA_VERSION, requestID=self.create_request_id()) + + +class OadrCreatePartyRegistrationBuilder(OadrBuilder): + + def __init__(self, xml_signature=None, ven_name=None, **kwargs): + super(OadrCreatePartyRegistrationBuilder, self).__init__(**kwargs) + self.xml_signature = xml_signature + self.ven_name = ven_name + + def build(self): + return oadr_20b.oadrCreatePartyRegistrationType(schemaVersion=SCHEMA_VERSION, + requestID=self.create_request_id(), + registrationID=None, + venID=self.ven_id, + oadrProfileName='2.0b', + oadrTransportName='simpleHttp', + oadrTransportAddress=None, + oadrReportOnly=False, + oadrXmlSignature=self.xml_signature, + oadrVenName=self.ven_name, + oadrHttpPullModel=True) + + +class OadrRequestEventBuilder(OadrBuilder): + + def __init__(self, **kwargs): + super(OadrRequestEventBuilder, self).__init__(**kwargs) + + def build(self): + ei_request_event = oadr_20b.eiRequestEvent(requestID=self.create_request_id(), venID=self.ven_id) + return oadr_20b.oadrRequestEventType(schemaVersion=SCHEMA_VERSION, eiRequestEvent=ei_request_event) + + +class OadrCreatedEventBuilder(OadrBuilder): + + """ + Construct an oadrCreatedEvent to return in response to an oadrDistributeEvent or oadrCreateEvent. + + If an error occurs while processing an events in the VTN's payload, return an oadrCreatedEvent + response in which the eiResponse has a normal/valid (200) code, + and the eventResponse has a responseCode containing the error code. + """ + + def __init__(self, event=None, error_code=None, error_message=None, **kwargs): + super(OadrCreatedEventBuilder, self).__init__(**kwargs) + self.event = event + self.error_code = error_code + self.error_message = error_message + + def build(self): + ei_response = oadr_20b.EiResponseType(responseCode=OADR_VALID_RESPONSE, requestID=None) + qualified_event_id = oadr_20b.QualifiedEventIDType(eventID=self.event.event_id, + modificationNumber=self.event.modification_number) + # OADR rule 42: the requestID from the oadrDistributeEvent must be re-used by the eventResponse. + event_response = oadr_20b.eventResponseType(responseCode=self.error_code or OADR_VALID_RESPONSE, + responseDescription=self.error_message, + requestID=self.event.request_id, + qualifiedEventID=qualified_event_id, + optType=self.event.opt_type) + # OADR rule 25, 35: eventResponses is required except when eiResponse indicates failure. + event_responses = oadr_20b.eventResponses() + event_responses.add_eventResponse(event_response) + ei_created_event = oadr_20b.eiCreatedEvent(eiResponse=ei_response, + eventResponses=event_responses, + venID=self.ven_id) + return oadr_20b.oadrCreatedEventType(eiCreatedEvent=ei_created_event) + + +class OadrReportBuilder(OadrBuilder): + + def __init__(self, report=None, report_request_id=None, pending_report_request_ids=None, **kwargs): + super(OadrReportBuilder, self).__init__(**kwargs) + self.report = report + self.report_request_id = report_request_id + self.pending_report_request_ids = pending_report_request_ids + + +class OadrRegisterReportBuilder(OadrReportBuilder): + + def __init__(self, reports=None, **kwargs): + super(OadrRegisterReportBuilder, self).__init__(**kwargs) + self.reports = reports + + def build(self): + oadr_reports = [self.build_metadata_oadr_report(report) for report in self.reports] + return oadr_20b.oadrRegisterReportType(schemaVersion=SCHEMA_VERSION, + requestID=self.create_request_id(), + oadrReport=oadr_reports, + venID=self.ven_id, + reportRequestID=None) + + def build_metadata_oadr_report(self, report): + descriptions = [] + for tel_vals in jsonapi.loads(report.telemetry_parameters).values(): + # Rule 305: For TELEMETRY_USAGE reports, units in reportDescription.itemBase should be powerReal. + if tel_vals['units'] == 'powerReal': + item_base = oadr_20b.PowerRealType(itemDescription='RealPower', + itemUnits='W', + siScaleCode=None, + powerAttributes=None) + else: + item_base = None + min_freq, max_freq = tel_vals['min_frequency'], tel_vals['max_frequency'] + desc = oadr_20b.oadrReportDescriptionType(rID=tel_vals['r_id'], + reportType=tel_vals['report_type'], + readingType=tel_vals['reading_type'], + itemBase=item_base, + oadrSamplingRate=self.build_sampling_rate(min_freq, max_freq)) + descriptions.append(desc) + rpt_interval_duration = isodate.duration_isoformat(timedelta(seconds=report.interval_secs)) + return oadr_20b.oadrReportType(duration=oadr_20b.DurationPropType(rpt_interval_duration), + oadrReportDescription=descriptions, + reportRequestID=None, + reportSpecifierID=report.report_specifier_id, + reportName=report.name, + createdDateTime=utils.get_aware_utc_now()) + + @staticmethod + def build_sampling_rate(min_freq, max_freq): + min_sampling_rate = isodate.duration_isoformat(timedelta(seconds=min_freq)) + max_sampling_rate = isodate.duration_isoformat(timedelta(seconds=max_freq)) + sampling_rate = oadr_20b.oadrSamplingRateType(oadrMinPeriod=min_sampling_rate, + oadrMaxPeriod=max_sampling_rate, + oadrOnChange=False) + return sampling_rate + + +class OadrUpdateReportBuilder(OadrReportBuilder): + + def __init__(self, telemetry=None, online=None, manual_override=None, **kwargs): + super(OadrUpdateReportBuilder, self).__init__(**kwargs) + self.telemetry = telemetry + self.online = online + self.manual_override = manual_override + + def build(self): + """ + Return an oadrReport containing telemetry updates. + + A typical XML element structure is: + + + + 2017-12-06T21:33:32Z + + + PT0S + + + + + 2017-12-06T21:33:08.423684Z + + + PT30S + + + baseline_power + + 6.2 + + + + actual_power + + 5.44668467252 + + + + Status + + true + false + + + + + RR_916fd571c0657070575a + telemetry + TELEMETRY_USAGE + 2017-12-06T21:33:47.869392Z + + + @return: (oadrReportType) An oadrReport. + """ + # To accommodate the Kisensum VTN server, a null report duration has a special meaning + # to the VEN: the report request should continue indefinitely, with no scheduled + # completion time. + # In this UpdateReport request, a null duration is sent as 0 seconds (one-time report) + # to ensure that the UpdateReport has a valid construction. + report_duration = self.report.duration if self.report.duration is not None else 'PT0S' + oadr_report = oadr_20b.oadrReportType(dtstart=oadr_20b.dtstart(date_time=self.report.start_time), + duration=oadr_20b.DurationPropType(report_duration), + reportRequestID=self.report.report_request_id, + reportSpecifierID=self.report.report_specifier_id, + reportName=self.report.name, + intervals=self.build_intervals(), + createdDateTime=utils.get_aware_utc_now()) + return oadr_20b.oadrUpdateReportType(schemaVersion=SCHEMA_VERSION, + requestID=self.create_request_id(), + oadrReport=[oadr_report], + venID=self.ven_id) + + def build_intervals(self): + """Build an intervals object, holding a list of Intervals to report in this update.""" + intervals = oadr_20b.intervals() + if self.report.report_specifier_id == 'telemetry': + # Build Intervals that report metrics. + for telemetry_values in self.telemetry: + intervals.add_interval(self.build_report_interval(telemetry_values)) + elif self.report.report_specifier_id == 'telemetry_status': + # OADR rule 331, 510: Build an Interval, in a telemetry_status report, giving current VEN status. + interval_start = isodate.parse_datetime(self.report.iso_last_report) + interval_duration = isodate.duration_isoformat(timedelta(seconds=self.report.interval_secs)) + interval = oadr_20b.IntervalType(dtstart=oadr_20b.dtstart(date_time=interval_start), + duration=oadr_20b.DurationPropType(duration=interval_duration)) + if self.online is not None or self.manual_override is not None: + payload = oadr_20b.oadrReportPayloadType(rID='Status') + payload_status = oadr_20b.oadrPayloadResourceStatusType(oadrOnline=self.online, + oadrManualOverride=self.manual_override) + payload_status.original_tagname_ = 'oadrPayloadResourceStatus' + payload.set_payloadBase(payload_status) + interval.set_streamPayloadBase([payload]) + else: + interval.set_streamPayloadBase([]) + intervals.add_interval(interval) + return intervals + + def build_report_interval(self, telemetry_values): + """Build an Interval for a report timeframe that includes telemetry values gathered during that time.""" + interval_start = telemetry_values.start_time + # duration_isoformat can yield fractional seconds, e.g. PT59.9S, resulting in a warning during XML creation. + interval_duration = isodate.duration_isoformat(telemetry_values.get_duration()) + interval = oadr_20b.IntervalType(dtstart=oadr_20b.dtstart(date_time=interval_start), + duration=oadr_20b.DurationPropType(duration=interval_duration)) + interval.set_streamPayloadBase(self.build_report_payload_list(telemetry_values)) + return interval + + def build_report_payload_list(self, telemetry_values): + """Build a list of ReportPayloads containing current telemetry.""" + report_payload_list = [] + for tel_val in jsonapi.loads(self.report.telemetry_parameters).values(): + payload = self.build_report_payload_float(telemetry_values, tel_val['r_id'], tel_val['method_name']) + if payload: + report_payload_list.append(payload) + return report_payload_list + + @staticmethod + def build_report_payload_float(telemetry_values, r_id, method_name): + """Build a single ReportPayload containing a PayloadFloat metric.""" + metric = getattr(telemetry_values, method_name)() + if metric is None: + payload = None + else: + payload = oadr_20b.oadrReportPayloadType(rID=r_id) + payload.original_tagname_ = 'oadrReportPayload' + payload_float = oadr_20b.PayloadFloatType(value=metric) + payload_float.original_tagname_ = 'payloadFloat' + payload.set_payloadBase(payload_float) + return payload + + +class OadrCreatedReportBuilder(OadrReportBuilder): + + def __init__(self, **kwargs): + super(OadrCreatedReportBuilder, self).__init__(**kwargs) + + def build(self): + ei_response = oadr_20b.EiResponseType(responseCode=OADR_VALID_RESPONSE, requestID=self.create_request_id()) + oadr_pending_reports = oadr_20b.oadrPendingReportsType(reportRequestID=self.pending_report_request_ids) + return oadr_20b.oadrCreatedReportType(schemaVersion=SCHEMA_VERSION, + eiResponse=ei_response, + oadrPendingReports=oadr_pending_reports, + venID=self.ven_id) + + +class OadrCanceledReportBuilder(OadrReportBuilder): + + def __init__(self, **kwargs): + super(OadrCanceledReportBuilder, self).__init__(**kwargs) + + def build(self): + # Note that -- oddly -- this message does NOT contain the reportRequestID of the canceled report. + # It does list all active reports in oadrPendingReports, though, + # so the VTN has sufficient information to know which report was canceled. + ei_response = oadr_20b.EiResponseType(responseCode=OADR_VALID_RESPONSE, requestID=self.request_id) + oadr_pending_reports = oadr_20b.oadrPendingReportsType(reportRequestID=self.pending_report_request_ids) + return oadr_20b.oadrCanceledReportType(schemaVersion=SCHEMA_VERSION, + eiResponse=ei_response, + oadrPendingReports=oadr_pending_reports, + venID=self.ven_id) + + +class OadrResponseBuilder(OadrBuilder): + + def __init__(self, response_code=None, response_description=None, **kwargs): + super(OadrResponseBuilder, self).__init__(**kwargs) + self.response_code = response_code + self.response_description = response_description + + def build(self): + ei_response = oadr_20b.EiResponseType(responseCode=self.response_code, + responseDescription=self.response_description, + requestID=self.request_id) + return oadr_20b.oadrResponseType(schemaVersion=SCHEMA_VERSION, + eiResponse=ei_response, + venID=self.ven_id) diff --git a/services/unsupported/OpenADRVenAgent/openadrven/oadr_common.py b/services/unsupported/OpenADRVenAgent/openadrven/oadr_common.py new file mode 100644 index 0000000000..5acc2f35c1 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/oadr_common.py @@ -0,0 +1,35 @@ +HTTP_STATUS_CODES = {200: '200 OK', + 201: '201 Created', + 204: '204 No Content', + 500: '500 Internal Error'} + +# OADR Response Codes +OADR_VALID_RESPONSE = '200' +OADR_MOD_NUMBER_OUT_OF_ORDER = '450' +OADR_BAD_DATA = '459' +OADR_BAD_SIGNAL = '460' +OADR_EMPTY_DISTRIBUTE_EVENT = '999' + +SCHEMA_VERSION = '2.0b' + + +class OpenADRException(Exception): + """Abstract superclass for exceptions in the Open ADR VEN agent.""" + + def __init__(self, message, error_code, *args): + super(OpenADRException, self).__init__(message, *args) + self.error_code = error_code + + +class OpenADRInterfaceException(OpenADRException): + """Use this exception when an error should be sent to the VTN as an OadrResponse payload.""" + + def __init__(self, message, error_code, *args): + super(OpenADRInterfaceException, self).__init__(message, error_code, *args) + + +class OpenADRInternalException(OpenADRException): + """Use this exception when an error should be logged but not sent to the VTN.""" + + def __init__(self, message, error_code, *args): + super(OpenADRInternalException, self).__init__(message, error_code, *args) diff --git a/services/unsupported/OpenADRVenAgent/openadrven/oadr_extractor.py b/services/unsupported/OpenADRVenAgent/openadrven/oadr_extractor.py new file mode 100644 index 0000000000..7accce57c2 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/openadrven/oadr_extractor.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from datetime import timedelta +import isodate +import logging +import random +import time + +from volttron.platform.agent import utils +from volttron.platform import jsonapi + +from .oadr_common import * + +utils.setup_logging() +_log = logging.getLogger(__name__) + + +class OadrExtractor(object): + """Extract oadr model objects received from the VTN as XML.""" + + def __init__(self, request=None, **kwargs): + self.request = request + + def extract_request_id(self): + request_id = self.request.requestID + if request_id is None: + raise OpenADRInterfaceException('Missing requestID', OADR_BAD_DATA) + return request_id + + @staticmethod + def extract_start_and_end_time(interval): + """Extract a start_time and an end_time from a received interval.""" + try: + start_time = interval.properties.dtstart.date_time + assert start_time is not None + assert start_time.tzinfo is not None + except Exception as err: + error_msg = 'Missing/Invalid interval properties.dtstart.date_time: {} {}'.format(start_time, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + try: + duration = interval.properties.duration.duration + parsed_duration = isodate.parse_duration(duration) + except Exception as err: + error_msg = 'Missing/Invalid interval properties.duration.duration: {} {}'.format(duration, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + # An interval with 0 duration has no defined endTime and remains active until canceled. + end_time = None if parsed_duration.total_seconds() == 0.0 else start_time + parsed_duration + return start_time, end_time + + +class OadrResponseExtractor(OadrExtractor): + + def __init__(self, ei_response=None, **kwargs): + super(OadrResponseExtractor, self).__init__(**kwargs) + self.ei_response = ei_response + + def extract(self): + """An eiResponse can appear in multiple kinds of VTN requests. Extract its code and description.""" + return self.ei_response.responseCode, self.ei_response.responseDescription + + +class OadrEventExtractor(OadrExtractor): + """Extract an event's properties from oadr model objects received from the VTN as XML.""" + + def __init__(self, event=None, ei_event=None, **kwargs): + super(OadrEventExtractor, self).__init__(**kwargs) + self.event = event + self.ei_event = ei_event + + def extract_event_descriptor(self): + """Extract eventDescriptor data from the received eiEvent, populating the temporary EiEvent.""" + event_descriptor = self.ei_event.eventDescriptor + + # OADR rule 13: Status value must be a valid type, appropriate for the event's period. + self.event.status = event_descriptor.eventStatus + if self.event.status not in self.event.STATUS_VALUES: + raise OpenADRInterfaceException('Missing or invalid eventDescriptor.eventStatus', OADR_BAD_DATA) + + self.event.modification_number = event_descriptor.modificationNumber + if self.event.modification_number is None: + raise OpenADRInterfaceException('Missing eventDescriptor.modificationNumber', OADR_BAD_DATA) + + not_used = event_descriptor.modificationReason + + if event_descriptor.priority: + self.event.priority = event_descriptor.priority + + market_context_holder = event_descriptor.eiMarketContext + if market_context_holder: + # OADR rule 48: Allow any valid URI as a marketContext (e.g., *). + not_used = market_context_holder.marketContext + not_used = event_descriptor.createdDateTime + test_flag = event_descriptor.testEvent + if test_flag: + self.event.test_event = test_flag + not_used = event_descriptor.vtnComment + + def extract_active_period(self): + """Validate eiActivePeriod data in the received eiEvent.""" + active_period = self.ei_event.eiActivePeriod + if active_period is None: + raise OpenADRInterfaceException('Missing eiEvent.eiActivePeriod', OADR_BAD_DATA) + + properties = active_period.properties + if properties is None: + raise OpenADRInterfaceException('Missing eiEvent.eiActivePeriod.properties', OADR_BAD_DATA) + + try: + self.event.dtstart = properties.dtstart.date_time + assert self.event.dtstart is not None + assert self.event.dtstart.tzinfo is not None + except Exception as err: + error_msg = 'Missing/Invalid properties.dtstart.date_time: {} {}'.format(properties.dtstart.date_time, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + try: + self.event.duration = properties.duration.duration + event_length = isodate.parse_duration(properties.duration.duration) + except Exception as err: + error_msg = 'Missing/Invalid properties.duration.duration: {} {}'.format(properties.duration.duration, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + active_period_props = active_period.properties + + if active_period_props.tolerance and active_period_props.tolerance.tolerate: + self.event.start_after = active_period_props.tolerance.tolerate.startafter + else: + self.event.start_after = None + + if self.event.start_after: + try: + max_offset = isodate.parse_duration(self.event.start_after) + # OADR rule 30: Randomize start_time and end_time if start_after is provided. + self.event.start_time = self.event.dtstart + timedelta(seconds=(max_offset.seconds * random.random())) + except Exception as err: + error_msg = 'Invalid activePeriod tolerance.tolerate.startafter: {}'.format(err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + else: + self.event.start_time = self.event.dtstart + + # An interval with 0 duration has no defined endTime and remains active until canceled. + self.event.end_time = self.event.start_time + event_length if event_length.total_seconds() > 0.0 else None + + notification = active_period_props.x_eiNotification + if notification is None: + # OADR rule 105: eiNotification is required as an element of activePeriod. + raise OpenADRInterfaceException('Missing eiActivePeriod.properties.eiNotification', OADR_BAD_DATA) + + not_used = notification.duration + + ramp_up = active_period_props.x_eiRampUp + if ramp_up is not None: + not_used = ramp_up.duration + recovery = active_period_props.x_eiRecovery + if recovery is not None: + not_used = recovery.duration + + def extract_signals(self): + """Extract eiEventSignals from the received eiEvent, populating the temporary EiEvent.""" + if not self.ei_event.eiEventSignals: + raise OpenADRInterfaceException('At least one event signal is required.', OADR_BAD_SIGNAL) + if not self.ei_event.eiEventSignals.eiEventSignal: + raise OpenADRInterfaceException('At least one event signal is required.', OADR_BAD_SIGNAL) + signals_dict = {s.signalID: self.extract_signal(s) for s in self.ei_event.eiEventSignals.eiEventSignal} + self.event.signals = jsonapi.dumps(signals_dict) + # Sum of all signal interval durations must equal the event duration. + signals_duration = timedelta(seconds=0) + for signal in self.ei_event.eiEventSignals.eiEventSignal: + for interval in signal.intervals.interval: + signals_duration += isodate.parse_duration(interval.duration.duration) + event_duration = isodate.parse_duration(self.event.duration) + if signals_duration != event_duration: + err_msg = 'Total signal interval durations {} != event duration {}'.format(signals_duration, event_duration) + raise OpenADRException(err_msg, OADR_BAD_SIGNAL) + + @staticmethod + def extract_signal(signal): + """Extract a signal from the received eiEvent.""" + if signal.signalName.lower() != 'simple': + raise OpenADRInterfaceException('Received a non-simple event signal; not supported by this VEN.', OADR_BAD_SIGNAL) + if signal.signalType.lower() != 'level': + # OADR rule 116: If signalName = simple, signalType = level. + # Disabling this validation since the EPRI VTN server sometimes sends type "delta" for simple signals. + # error_msg = 'Simple signalType must be level; = {}'.format(signal.signalType) + # raise OpenADRInterfaceException(error_msg, OADR_BAD_SIGNAL) + pass + return { + 'signalID': signal.signalID, + 'currentLevel': int(signal.currentValue.payloadFloat.value) if signal.currentValue else None, + 'intervals': { + interval.uid if interval.uid and interval.uid.strip() else str(i): { + 'uid': interval.uid if interval.uid and interval.uid.strip() else str(i), + 'duration': interval.duration.duration, + 'payloads': {'level': int(payload.payloadBase.value) for payload in interval.streamPayloadBase} + } for i, interval in enumerate(signal.intervals.interval)} + } + + +class OadrReportExtractor(OadrExtractor): + """Extract a report's properties from oadr model objects received from the VTN as XML.""" + + def __init__(self, report=None, report_parameters=None, **kwargs): + super(OadrReportExtractor, self).__init__(**kwargs) + self.report = report + self.report_parameters = report_parameters + + def extract_report_request_id(self): + """Extract and return the report's reportRequestID.""" + report_request_id = self.request.reportRequestID + if report_request_id is None: + raise OpenADRInterfaceException('Missing oadrReportRequest.reportRequestID', OADR_BAD_DATA) + return report_request_id + + def extract_specifier_id(self): + """Extract and return the report's reportSpecifierID.""" + report_specifier = self.request.reportSpecifier + if report_specifier is None: + raise OpenADRInterfaceException('Missing oadrReportRequest.reportSpecifier', OADR_BAD_DATA) + report_specifier_id = report_specifier.reportSpecifierID + if report_specifier_id is None: + error_msg = 'Missing oadrReportRequest.reportSpecifier.reportSpecifierID' + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + return report_specifier_id + + def extract_report(self): + """Validate various received report fields and add them to the report instance.""" + report_specifier = self.request.reportSpecifier + report_interval = report_specifier.reportInterval + if report_interval is None: + raise OpenADRInterfaceException('Missing reportInterval', OADR_BAD_DATA) + + try: + start_time = report_interval.properties.dtstart.date_time + assert start_time is not None + assert start_time.tzinfo is not None + except Exception as err: + error_msg = 'Missing/Invalid interval properties.dtstart.date_time: {} {}'.format(start_time, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + try: + duration = report_interval.properties.duration.duration + end_time = start_time + isodate.parse_duration(duration) + except Exception as err: + # To accommodate the Kisensum VTN server, a report interval with a missing/null duration + # has a special meaning to the VEN: the report request continues indefinitely, + # with no scheduled completion time. + _log.debug('Missing/null report interval duration: the report will remain active indefinitely.') + duration = None + end_time = None + + self.report.start_time = start_time + self.report.end_time = end_time + self.report.duration = duration + + self.report.name = self.report_parameters.get('report_name', None) + self.report.telemetry_parameters = jsonapi.dumps(self.report_parameters.get('telemetry_parameters', None)) + + default = self.report_parameters.get('report_interval_secs_default') + iso_duration = report_specifier.reportBackDuration + if iso_duration is not None: + try: + self.report.interval_secs = int(isodate.parse_duration(iso_duration.duration).total_seconds()) + except Exception as err: + error_msg = 'reportBackDuration {} has unparsable duration: {}'.format(dur, err) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + elif default is not None: + try: + self.report.interval_secs = int(default) + except ValueError: + error_msg = 'Default report interval {} is not an integer number of seconds'.format(default) + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + else: + self.report.interval_secs = None + + if report_specifier.granularity: + try: + granularity = isodate.parse_duration(report_specifier.granularity.duration) + self.report.granularity_secs = int(granularity.total_seconds()) + except Exception: + error_msg = 'Report granularity is missing or is not an ISO8601 duration' + raise OpenADRInterfaceException(error_msg, OADR_BAD_DATA) + + +class OadrRegistrationExtractor(OadrExtractor): + + def __init__(self, **kwargs): + super(OadrRegistrationExtractor, self).__init__(**kwargs) + + +class OadrCreatedPartyRegistrationExtractor(OadrRegistrationExtractor): + + def __init__(self, registration=None, **kwargs): + super(OadrCreatedPartyRegistrationExtractor, self).__init__(**kwargs) + self.registration = registration + + def extract_ven_id(self): + return self.registration.venID + + def extract_poll_freq(self): + return self.registration.oadrRequestedOadrPollFreq + + def extract_vtn_id(self): + return self.registration.vtnID diff --git a/services/unsupported/OpenADRVenAgent/requirements.txt b/services/unsupported/OpenADRVenAgent/requirements.txt new file mode 100644 index 0000000000..41562db52f --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/requirements.txt @@ -0,0 +1,4 @@ +isodate==0.6 +signxml==2.4 +SQLAlchemy==1.1.4 +numpy==1.13.0 \ No newline at end of file diff --git a/services/unsupported/OpenADRVenAgent/setup.py b/services/unsupported/OpenADRVenAgent/setup.py new file mode 100644 index 0000000000..cc64769bff --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/setup.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +from os import path +from setuptools import setup, find_packages + +MAIN_MODULE = 'agent' + +# Find the agent package that contains the main module +packages = find_packages('.') +agent_package = '' +for package in find_packages(): + # Because there could be other packages such as tests + if path.isfile(package + '/' + MAIN_MODULE + '.py') is True: + agent_package = package +if not agent_package: + raise RuntimeError('None of the packages under {dir} contain the file ' + '{main_module}'.format(main_module=MAIN_MODULE + '.py', + dir=path.abspath('.'))) + +# Find the version number from the main module +agent_module = agent_package + '.' + MAIN_MODULE +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) +__version__ = _temp.__version__ + +# Setup +setup( + name=agent_package + 'agent', + version=__version__, + install_requires=['volttron'], + packages=packages, + entry_points={ + 'setuptools.installation': [ + 'eggsecutable = ' + agent_module + ':main', + ] + } +) diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/IDENTITY b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/IDENTITY new file mode 100644 index 0000000000..5645dfdbc8 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/IDENTITY @@ -0,0 +1 @@ +controlagentsim \ No newline at end of file diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/__init__.py b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim.config b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim.config new file mode 100644 index 0000000000..4ea5e8fd26 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim.config @@ -0,0 +1,7 @@ +{ + "venagent_id": "venagent", # Volttron ID of the VEN agent + "opt_type": "optIn", # optIn or optOut + "report_interval_secs": 30, # How often to issue RPCs to the VEN agent + "baseline_power_kw": 6.2, # Simulated baseline power measurement (constant) + "sine_period_secs": 600 # Period of the simulated actual-measurement sine wave +} diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/__init__.py b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/agent.py b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/agent.py new file mode 100644 index 0000000000..a73da34166 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/controlagentsim/agent.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + + + +from datetime import timedelta +import logging +import numpy +import sys + +from volttron.platform.agent import utils +from volttron.platform.vip.agent import Agent, Core, RPC +from volttron.platform.messaging import topics +from volttron.platform.scheduling import periodic + +utils.setup_logging() +_log = logging.getLogger(__name__) + +__version__ = '1.0' + + +def control_agent(config_path, **kwargs): + """ + Parse the ControlAgentSim configuration file and return an instance of + the agent that has been created using that configuration. + + See initialize_config() method documentation for a description of each configurable parameter. + + :param config_path: (str) Path to a configuration file. + :returns: ControlAgentSim instance + """ + try: + config = utils.load_config(config_path) + except Exception as err: + _log.error("Error loading configuration: {}".format(err)) + config = {} + venagent_id = config.get('venagent_id') + opt_type = config.get('opt_type') + report_interval_secs = config.get('report_interval_secs') + baseline_power_kw = config.get('baseline_power_kw') + sine_period_secs = config.get('sine_period_secs') + return ControlAgentSim(venagent_id, opt_type, report_interval_secs, baseline_power_kw, sine_period_secs, **kwargs) + + +class ControlAgentSim(Agent): + """ + This is a sample ControlAgent for use while demonstrating and testing OpenADRVenAgent. + It exercises the VEN agent's exposed RPC methods, and consumes messages published by + OpenADRVenAgent. + """ + + def __init__(self, venagent_id, opt_type, report_interval_secs, baseline_power_kw, sine_period_secs, **kwargs): + super(ControlAgentSim, self).__init__(**kwargs) + self.venagent_id = None + self.default_opt_type = None + self.report_interval_secs = None + self.baseline_power_kw = None + self.sine_period_secs = None + self.default_config = {'venagent_id': venagent_id, + 'opt_type': opt_type, + 'report_interval_secs': report_interval_secs, + 'baseline_power_kw': baseline_power_kw, + 'sine_period_secs': sine_period_secs} + self.vip.config.set_default("config", self.default_config) + self.vip.config.subscribe(self._configure, actions=["NEW", "UPDATE"], pattern="config") + self.initialize_config(self.default_config) + + def _configure(self, config_name, action, contents): + """The agent's config may have changed. Re-initialize it.""" + config = self.default_config.copy() + config.update(contents) + self.initialize_config(config) + + def initialize_config(self, config): + """ + Initialize the Control Agent's configuration. + + venagent_id : (String) Volttron ID of the VEN agent + default_opt_type : (String) optIn or optOut + report_interval_secs : (Integer) How often to issue RPCs to the VEN agent + baseline_power_kw : (Fixed Point) Simulated baseline power measurement (constant) + sine_period_secs : (Integer) Period of the simulated actual-measurement sine wave + """ + _log.debug("Configuring agent") + self.venagent_id = config.get('venagent_id') + self.default_opt_type = config.get('opt_type') + self.report_interval_secs = config.get('report_interval_secs') + self.baseline_power_kw = config.get('baseline_power_kw') + self.sine_period_secs = config.get('sine_period_secs') + + _log.debug('Configuration parameters:') + _log.debug('\tvenagent_id={}'.format(self.venagent_id)) + _log.debug('\tOptIn/OptOut={}'.format(self.default_opt_type)) + _log.debug('\tReport interval (secs)={}'.format(self.report_interval_secs)) + _log.debug('\tBaseline power (kw)={}'.format(self.baseline_power_kw)) + _log.debug('\tSine wave period (secs)={}'.format(self.sine_period_secs)) + + @Core.receiver('onstart') + def onstart_method(self, sender): + """The agent has started. Perform initialization and spawn the main process loop.""" + _log.debug('Starting agent') + + # Subscribe to the VENAgent's event and report parameter publications. + self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.OPENADR_EVENT, callback=self.receive_event) + self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.OPENADR_STATUS, callback=self.receive_status) + + self.core.schedule(periodic(self.report_interval_secs), self.issue_rpcs) + + def issue_rpcs(self): + """Periodically issue RPCs, including report_sample_telemetry, to the VEN agent.""" + self.report_sample_telemetry() + self.get_events() + self.get_report_parameters() + self.set_telemetry_status(online='True', manual_override='False') + + def report_sample_telemetry(self): + """ + At regular intervals, send sample metrics to the VEN agent as an RPC. + + Send measurements that simulate the following: + - Constant baseline power + - Measured power that is a sine wave with amplitude = baseline power + """ + + def sine_wave(t, p): + """Return the current value at time t of a sine wave from -1 to 1 with period p.""" + seconds_since_hour = (60.0 * int(t.strftime('%M'))) + int(t.strftime('%S')) + fraction_into_period = (seconds_since_hour % float(p)) / float(p) + return numpy.sin(2 * numpy.pi * fraction_into_period) + + end_time = utils.get_aware_utc_now() + start_time = end_time - timedelta(seconds=self.report_interval_secs) + val = sine_wave(end_time, self.sine_period_secs) + # Adjust the sine wave upward so that all values are positive, with amplitude = self.baseline_power_kw. + measurement_kw = self.baseline_power_kw * ((val + 1) / 2) + self.report_telemetry({'baseline_power_kw': str(self.baseline_power_kw), + 'current_power_kw': str(measurement_kw), + 'start_time': start_time.__str__(), + 'end_time': end_time.__str__()}) + + def receive_event(self, peer, sender, bus, topic, headers, message): + """(Subscription callback) Receive a list of active events as JSON.""" + debug_string = 'Received event: ID={}, status={}, start={}, end={}, opt_type={}, all params={}' + _log.debug(debug_string.format(message['event_id'], + message['status'], + message['start_time'], + message['end_time'], + message['opt_type'], + message)) + if message['opt_type'] != self.default_opt_type: + # Send an optIn decision to the VENAgent. + self.respond_to_event(message['event_id'], self.default_opt_type) + + def receive_status(self, peer, sender, bus, topic, headers, message): + """(Subscription callback) Receive a list of report parameters as JSON.""" + debug_string = 'Received report parameters: request_id={}, status={}, start={}, end={}, all params={}' + _log.debug(debug_string.format(message['report_request_id'], + message['status'], + message['start_time'], + message['end_time'], + message)) + _log.debug('Received report(s) status: {}'.format(message)) + + def respond_to_event(self, event_id, opt_type): + """ + (Send RPC) Respond to an event, telling the VENAgent whether to opt in or out. + + @param event_id: (String) ID of an event. + @param opt_type: (String) Whether to optIn or optOut of the event. + """ + _log.debug('Sending an {} response for event ID {}'.format(opt_type, event_id)) + self.send_rpc('respond_to_event', event_id, opt_type) + + def get_events(self): + """ + (Send RPC) Request a JSON list of events from the VENAgent. + + @return: (JSON) A list of events. + """ + _log.debug('Requesting an event list') + events_list = self.send_rpc('get_events') + if events_list: + for event_dict in events_list: + _log.debug('\tevent_id {}:'.format(event_dict.get('event_id'))) + for k, v in event_dict.items(): + _log.debug('\t\t{}={}'.format(k, v)) + else: + _log.debug('\tNo active events') + + def get_report_parameters(self): + """ + (Send RPC) Request a JSON list of report parameters from the VENAgent. + + This method dumps the contents of the returned dictionary of report parameters as debug output. + """ + _log.debug('Requesting report parameters') + param_dict = self.send_rpc('get_telemetry_parameters') + if param_dict: + for key, val in param_dict.items(): + try: + if type(val) == dict: + _log.debug('\t{}:'.format(key)) + for key2, val2 in val.items(): + if type(val2) == dict: + _log.debug('\t\t{}:'.format(key2)) + for key3, val3 in val2.items(): + _log.debug('\t\t\t{}={}'.format(key3, val3)) + else: + _log.debug('\t\t{}={}'.format(key2, val2)) + else: + _log.debug('\t{}={}'.format(key, val)) + except ValueError: + _log.debug('\t{}={}'.format(key, val)) + else: + _log.debug('\tNo report parameters') + + def set_telemetry_status(self, online=None, manual_override=None): + """ + (Send RPC) Update the VENAgent's reporting status. + + @param online: (Boolean) Whether the VENAgent's resource is online. + @param manual_override: (Boolean) Whether resource control has been overridden. + """ + _log.debug('Setting telemetry status: online={}, manual_override={}'.format(online, manual_override)) + self.send_rpc('set_telemetry_status', online, manual_override) + + def report_telemetry(self, telemetry): + """ + (Send RPC) Update the VENAgent's report metrics. + + @param telemetry: (JSON) Current value of each report metric. + """ + _log.debug('Reporting telemetry: {}'.format(telemetry)) + self.send_rpc('report_telemetry', telemetry=telemetry) + + def send_rpc(self, rpc_name, *args, **kwargs): + """Send an RPC request to the VENAgent, and return its response (if any).""" + response = self.vip.rpc.call(self.venagent_id, rpc_name, *args, **kwargs) + return response.get(30) + + +def main(): + """Start the agent.""" + utils.vip_main(control_agent, identity='controlagentsim', version=__version__) + + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + pass diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/install-control-agent.sh b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/install-control-agent.sh new file mode 100644 index 0000000000..7e0db40032 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/install-control-agent.sh @@ -0,0 +1,8 @@ +cd $VOLTTRON_ROOT +export VIP_SOCKET="ipc://$VOLTTRON_HOME/run/vip.socket" +python scripts/install-agent.py \ + -s $VOLTTRON_ROOT/services/core/OpenADRVenAgent/test/ControlAgentSim \ + -i control_agent_sim \ + -c $VOLTTRON_ROOT/services/core/OpenADRVenAgent/test/ControlAgentSim/controlagentsim.config \ + -t control_agent_sim \ + -f \ No newline at end of file diff --git a/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/setup.py b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/setup.py new file mode 100644 index 0000000000..6c654a91dc --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/ControlAgentSim/setup.py @@ -0,0 +1,34 @@ +from os import path +from setuptools import setup, find_packages + +MAIN_MODULE = 'agent' + +# Find the agent package that contains the main module +packages = find_packages('.') +agent_package = '' +for package in find_packages(): + # Because there could be other packages such as tests + if path.isfile(package + '/' + MAIN_MODULE + '.py') is True: + agent_package = package +if not agent_package: + raise RuntimeError('None of the packages under {dir} contain the file ' + '{main_module}'.format(main_module=MAIN_MODULE + '.py', + dir=path.abspath('.'))) + +# Find the version number from the main module +agent_module = agent_package + '.' + MAIN_MODULE +_temp = __import__(agent_module, globals(), locals(), ['__version__'], 0) +__version__ = _temp.__version__ + +# Setup +setup( + name=agent_package + 'agent', + version=__version__, + install_requires=['volttron'], + packages=packages, + entry_points={ + 'setuptools.installation': [ + 'eggsecutable = ' + agent_module + ':main', + ] + } +) diff --git a/services/unsupported/OpenADRVenAgent/test/__init__.py b/services/unsupported/OpenADRVenAgent/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/unsupported/OpenADRVenAgent/test/crypto_experiment.py b/services/unsupported/OpenADRVenAgent/test/crypto_experiment.py new file mode 100644 index 0000000000..aa15cd0285 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/crypto_experiment.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- {{{ +# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# +# Copyright 2020, Battelle Memorial Institute. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This material was prepared as an account of work sponsored by an agency of +# the United States Government. Neither the United States Government nor the +# United States Department of Energy, nor Battelle, nor any of their +# employees, nor any jurisdiction or organization that has cooperated in the +# development of these materials, makes any warranty, express or +# implied, or assumes any legal liability or responsibility for the accuracy, +# completeness, or usefulness or any information, apparatus, product, +# software, or process disclosed, or represents that its use would not infringe +# privately owned rights. Reference herein to any specific commercial product, +# process, or service by trade name, trademark, manufacturer, or otherwise +# does not necessarily constitute or imply its endorsement, recommendation, or +# favoring by the United States Government or any agency thereof, or +# Battelle Memorial Institute. The views and opinions of authors expressed +# herein do not necessarily state or reflect those of the +# United States Government or any agency thereof. +# +# PACIFIC NORTHWEST NATIONAL LABORATORY operated by +# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY +# under Contract DE-AC05-76RL01830 +# }}} + +import signxml +import lxml.etree as etree_ +import io +import openadrven.oadr_20b + +# Run this experiment from $VOLTTRON_ROOT/services/core/OpenADRVenAgent: "python crypto_experiment.py" + +# X509 certificates generated by Kyrio +CERTS_DIRECTORY = 'certs/' +CERT_FILENAME = 'TEST_RSA_VEN_171024145702_cert.pem' # The VEN certificate issued by the CA. +KEY_FILENAME = 'TEST_RSA_VEN_171024145702_privkey.pem' # The VEN's private key. +VTN_CA_CERT_FILENAME = 'TEST_OpenADR_RSA_BOTH0002_Cert.pem' # The concatenated root and intermediate certificates. + +XML_PREFIX = '\n' +PAYLOAD_START_TAG = '' +PAYLOAD_END_TAG = '' + +def pretty_print_lxml(label, lxml_string): + if label: + print(label) + print(etree_.tostring(lxml_string, pretty_print=True)) + + +HAND_BUILT_PAYLOAD = False + +signed_object_xml = open('test/xml/crypto_experiment.xml', 'rb').read() +signed_object_lxml = etree_.fromstring(signed_object_xml) + +pretty_print_lxml('Original XML to be signed:', signed_object_lxml) +# Use "detached method": the signature lives alonside the signed object in the XML element tree. +# Use c14n "exclusive canonicalization": the signature is independent of namespace inclusion/exclusion. +signer = signxml.XMLSigner(method=signxml.methods.detached, + c14n_algorithm='http://www.w3.org/2001/10/xml-exc-c14n#') +signature_lxml = signer.sign(signed_object_lxml, + key=open(CERTS_DIRECTORY + KEY_FILENAME, 'rb').read(), + cert=open(CERTS_DIRECTORY + CERT_FILENAME, 'rb').read()) +pretty_print_lxml('Signed root:', signature_lxml) +signature_xml = etree_.tostring(signature_lxml) + +if HAND_BUILT_PAYLOAD: + payload = XML_PREFIX + PAYLOAD_START_TAG + signature_xml + signed_object_xml + PAYLOAD_END_TAG +else: + payload = etree_.Element("{http://openadr.org/oadr-2.0b/2012/07}oadrPayload", + nsmap=signed_object_lxml.nsmap) + payload.append(signature_lxml) + payload.append(signed_object_lxml) + pretty_print_lxml('Payload:', payload) + +# Confirm that the signed payload can be verified. +verif = signxml.XMLVerifier().verify(payload, ca_pem_file=CERTS_DIRECTORY + VTN_CA_CERT_FILENAME) +verified_data_lxml = verif.signed_xml +pretty_print_lxml('Verified data:', verified_data_lxml) diff --git a/services/unsupported/OpenADRVenAgent/test/curl_event.sh b/services/unsupported/OpenADRVenAgent/test/curl_event.sh new file mode 100644 index 0000000000..80dbeea3c6 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/curl_event.sh @@ -0,0 +1,5 @@ +echo Issuing EiEvent request to VEN with xml/$1.xml +curl -X POST -d @xml/$1.xml \ + --header "Content-Type:application/xml" \ + -v \ + http://127.0.0.1:8080/OpenADR2/Simple/2.0b/EiEvent diff --git a/services/unsupported/OpenADRVenAgent/test/curl_report.sh b/services/unsupported/OpenADRVenAgent/test/curl_report.sh new file mode 100644 index 0000000000..895b067041 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/curl_report.sh @@ -0,0 +1,5 @@ +echo Issuing EiReport request to VEN with xml/$1.xml +curl -X POST -d @xml/$1.xml \ + --header "Content-Type:application/xml" \ + -v \ + http://127.0.0.1:8080/OpenADR2/Simple/2.0b/EiReport diff --git a/services/unsupported/OpenADRVenAgent/test/curl_vtn_poll.sh b/services/unsupported/OpenADRVenAgent/test/curl_vtn_poll.sh new file mode 100644 index 0000000000..e045da28f7 --- /dev/null +++ b/services/unsupported/OpenADRVenAgent/test/curl_vtn_poll.sh @@ -0,0 +1,5 @@ +echo Issuing EiPoll request to EPRI VTN server with xml/$1.xml +curl -X POST -d @xml/$1.xml \ + --header "Content-Type:application/xml" \ + -v \ + http://openadr-vtn.ki-evi.com:4447/OpenADR2/Simple/2.0b/OadrPoll diff --git a/services/unsupported/OpenADRVenAgent/test/oadr20b_schema/dkuhlman-generateds-cba0ef052d1e.zip b/services/unsupported/OpenADRVenAgent/test/oadr20b_schema/dkuhlman-generateds-cba0ef052d1e.zip new file mode 100644 index 0000000000000000000000000000000000000000..9324fdb424d04d4ee1af1352c985a8a0b21d1eb9 GIT binary patch literal 923534 zcmbrlQ+TJ(wlx}?oup&iwr$(ClV5Dxwr$(CI=0zyI!?YmXP;+1>%Y#;-ut_#i@K?I zR?S&sjydLA3euops6aqKP(YBavC?H-;935EXZ3#vBM>x@iM5M`jjf>_t+}b4sgohV z)Wn(A*vOE<)Qo|Z$%N6Ap3cHt-_Xg}!qU~yh7RBkP*nj306Ydv(gcBb@7qJko_qW}6ric*){Fay%n1KIngFrfWwNY{klCo;> znMwFmq}aV&hEi|aoe5ACthpPUmRGLs$o-c? zyt!Su*@FvfgH;2-=0-epTabt~8mt1vgi1t;2E_W@RZ4}wYA3=Gq?0$l^&|erzVB zFL<~_CcH7EkmiCJdS~#92#c@V03t6ZoAU2MV^CW@_-!_#AIVwsyKB{&Ff4~!$Qku` zQYT1qBry9st<4`?YZETsCq!$jQZ-A5kM9q4DCBzF1e1cjv3SR!$a3-b-b`x>ZI@53 zAdI-H1dg}LEuJ7}4BO#S;-71xhtX&eGiF^OpzH>K3#Djy$K_t^bNY_cUu|1J; z<$}xsy$_U3RUT5hmy*1%PEXjMGkGxqJWie6q1-PmEEqFsCS{aybwxM2fJNi>j;q*( z>s>l=7>k*fGS4grZqbM&57zB!IZPOCbh;HNoyc(JCALtFXVYQsD`8WCrMrBMYFW4a zppqm+Y0+S?gi4{LWFa=29an)aP}7+uLOs%KCj&@!X;bW$7GDG$F?g>L?pixnu~uREs{3jsbhrT z^-t{Hh6sV8!_=G^;E%$J#>uCK;RsA*QODHEkFN{O7ef@2YjdO5S=0&Z;e(E)1Xh-- zkt67!VG=P@z@Tf_OrjK-bRcb+NlTU$%+$)6LN!9K9F*xW@F7}DjMym;!YZXA=jj0! zmXJG+4tbLO;8Aa$>@YO+moCRO^`l5OWIR$EKQ5WzO~M0Fay4XL9-=VM$cL72NA<^# zJ0p5WJ8D+K(S|XU0_pD<2TnH}^Y9Ua4-xIx57I})C5y<{Vm@udE!xKbu~%VqtRj#9 zNL*(H5XSDOr*j~%qAc+x00!hh6!MsIj(CI@Tyv*8B%I0QP==4|o!q2Qitb%rKxfQ5Pax9w_ami>GZTHF zEK(y}+GRJ$)waaptc~lOJ-QeW8FD8A2NWfQ`?jjC!aUu8DK;&h#8i!v1F|(p* z<=V!_CAqdnOJv^M0KG9bbz*y|7<_q)v;y55XvZZ<3 zmESuDCkps>f1o;Avu*~Qx^yKC*)`m|{Z}T9P@L?a*46c6eO$A9lLkV^@8j|1$8YHh z8PCcPpYHk*1b{s>sti|tGYxq5?C7#A7uuk$oW4JTBG0}QGsxV$MaFVbagT(WRuQG7Fbg$S zxj>Y?4YRQ=yd1wQzZ_8Yy8E!Ts^4@$7=tGtch^4w)w)hc7&Hr?Uy34KUFFw=Zl`0| zx_~^dZy-Tv+N@W<@mC6~e*f8_dSP_jtzB(w0uM0z?8GOyV6u-IE2Cek3qp|7GEhwF z^ClBNuGz3Aj@$~U4|Idpf@O1f%AW}!v}|U|X>1X!$y;6(;GPY(H9@-AjxCix;RzW+ z$cjX-a44ajm2IZ{0T@m~)Hl&Kh9M#%SwyB=-JBl(z3?GtQt} zL&v>VHd*@dyx_%J`d#_JhAmm9OsA-icx0Y4MDBX_xIl27;;x zc_8j46HYNVF58BHU!?N#L8Y?CE;^3S6N-mE3n(jw-7GJ1SGnwl_4t#vgWrVN%cc(@ z{&W;+rgX;2(LN0IgAN|NIGh6c7*J=8Hghs~{kR>R<6kuGMFvgi6+n{HUa@|6d-{`( zHctDa6#}^~Ah6pBMF(G21c3Z!}|CzV4G&M9&{xX}UzqyO+f1kJ9 zZEgOQxPGVfAOtd?hPdS(ghJ*I>PQGMdHs?Rv6IVEI0)uW$df=g2{M%@V*)~zY_L6E3x2o^>J@$7gG$;L0h zSq_OYSHtrTyhpibh4k47rt*C^+21DF*U!4qtvSc2pJLf}$c3!Nlt5BP8CZiP^UNu( zb~=31gtV@C8lI1|C9kTBly$e5bdVr5o@|;V|OW}>k;AmoGx{ECob}1t4Sd!kA@}Jg+A!trZomjebkLPP>8rKr+~s-E-IN`T?0XJAU%L_l^?K-SX15fu^CfS%nJv_vjApyzwtwx``j`w+XQZm(Vdf;qu zWu9WlZbb#4P0L)JvbJ%p`V<4gb=Fh4sO6Ou`*usCkUP*zp`GKD@H_Gsh8%UXgp!tq zq*doi*al92ps9Pg!*RL{{A;fmt?5h~skQr;TnSAbx6=Ed2}tiBw@spoT9REc*ZZFw z%O%dD`Dj`2;q7o&TgAw|1zLN!A~|}SAg)-yH`25osTqdFS*pZZn*hDP14l#ISQGKfc8PGr@j71aqF{0 zNw4=8w{-vF7VrQ1S^-R*0snrZYLceph8U1UpI;*t*(8e%TC_#ZKpJSF1`sBgW;8Hf zkv}ZnU)MYms7%AyB}bW0^}NV36mlByqNhsR_Dbd@?yeNu*5vN;L!QsPQ%xZ!vV&G% z?xw4UoHmFkANz6C+AxQgi6HsSIg`D&B&g>e=tc&7G6}Tn&w`fw1+aaZ*s=D z(lm@emd4OosFRAtQ${ez%}JxIYNaPhw8So-NBMmr$)*xig%d03uJEEGIX~gx+eg}s6NYZz zwdiO?giJVPIKZqnEAv4t3wx^av4EgDH3x(!;{L&M-SN_Hbb#R7K9whMRJHDddS?_L zg6v0#Xv#rjl)WRx5|7?F!6i!ntw(uUj#h<@^_rUF6*#06tFi{lnJvVh2LWH!{J!Ac; zyr?iekD!?fo)lo3Seu1!42jvg-|w?)jmGww!g27h>nV>r)7=>!h&8eK0D%l|3<1xy zU{_=?5nJvce{{yLXV&7M1#urPtNB9&KYY9Cy9&VE^Q$(n59c;m6|93qkdZp~@|uNQ zwtmB$&7&+G>Uu=LLsKN>Yb=6%GHX=Z`U;V$2;{O7sH ztdQgPqC82^qW$a*ikQ#@Y!toRez=)_P1^T`pcT^~K6ZetC}_N->;fTk8Zx>H6%M`u zyc$MFjLqfPZKbyF9A38DMq$*Y>=EaPwzup!#8l;?tQ1~qfLZ%Lxn0!z`T>R3ko=zIw&$O4z*UJ1V1hy^Eb`pIvx7(3Y-McjEApu9%M- zZQakZ!2Q*;j)LbM+oHQI!J2=FEsTkDQmrg)sR%FJ;qt@Uy@4M{w8tqnn6almcsW-+ z$=TO}BI`Hx?(65#)X0?@zQrSHRDf|?^Y?hNvBqEV=@O>WIcssD5~<8=(L{?iwXE8N3$X}tay+pdHkKxa z0DC8AdNCV&Hx&;D(|<=JWd(o#X_k=2;p{~faBOf;YC3Qdg=8%yt>m)3)O6)+m^2A+ zr+O+_N-Ejo1CamV9m8qv#hibw()>Gw|8FdFFm*CEwfhH5{+D6kG5@kGJYAGE?S=@DKIbS4ar&G4kR4+K8KR9<*cwD;e{&g834d=T7R#_X%2nTHZ(1T>_LIBRV&N{X>KPskn4q9^^DvDp465na=M%0{qu$Df6gH}b^r z%i0Xtt3HFdmvQJTs4U0M!si#70F$j%%8QVCp>WRAV<7#Ts+Og%-WG{|&o>&!G~O1! z%^50ZFPsdC2lh7u^~B`hB}YU2aAaf(8VBF-V^bmqHK`$raaH36#h$x#A$Kd;Ue7#h zm1~ldC<&!XvQY*SLO#@XDBB4Y(>oYs*;{L1(&Xcl zr!8R%`Z87Q;jC!%bm*i|2KPsxG{mzdw2xS5oVN%jVyK<)h4V=wjN~$5Cb!@jTh)qO z8)Nfmp!NufP#NL9JkNKg1VS&`To+#&QHpz4&)MpFxjuJlO#3;WwB!pI|Mn8eLWR@1 zbQB}~vib&8afrGaI0v9@6#N*~8S0V{(sR!X&B5hWlS9zJh0(D2Nqr(g{5B5iD88-j{IO73i62xyh->|MO|A71WMr}gz1~jx~VgZvtA(ehb%uJ^#X2* zLj|MU#PKkbs@^AQd0mgl>|xZVCh#*`kAe>Y*T{zub3QG1AhX{_+CS$xGjbtKDzw)W zZ_dStf5ps(F#SQ8Tr(6x^9pljtyz(;G&<2xq0CVm$b~gwfHR>7$Trn!11GpJAonxV z3LZ~RB2q1>8JrDJ!-h5|N%T$Bo5b(f@9{6)eYuX^eG*{A_y|R6EaIFX;%%Sb-TO0T zpF;lH0lH8Nt3V5BNNA*}wfWqUXb}nlMTyT%W|>oQ_#oh1;|Ip&AI@lxj_L2n2#je1 zJ24F0kvh!ki$oZ70>)Jm#Us&dI=gmGv`YN_npvRtgaKApAtu^|!?4_G&blM$chcJTD>{_rND;7k?Dc5Ymg}aCxpox??V)958IPPoitF?gp1go*E6pbaB;-v(8NQ&I?_cJ*{98JnjWS+3qb2=S!A3r$-bIQhU=i z^JqShUU{xEI!6=ElIf1NA`-{LtC6FM2J-tA*L#7E(BK@fkQpmaqR`1E<}{Z~7#2L= zeyK^dXO$$voB<$B7-JP{XJ)n{KA4j-q8royhk@zOAjQM2jvndR0&=?4DRVkY2P>k@ zJfQik=wbPCnrRF5CyGZA1O~m#f_MfayygwxvfaC96;t8#m${)M;Kmb65E(oQ^Osvz z!vpJd8uEUV3PUF!WU?J;_@v_}KNNDVYGUyH*Xx`sLxG&dp8WtAbk?9f=7gNFQD^eo^)vaH9(&De!SU@a!ru~7sCvw2 z%C{UNQq9`fno8g(qyvsCO7dbeW@WgX*gwkdl5|}LMBE0-kLbN<6tC{&yIDuC0>h_hNg%4^b*{nR8G@Z<7PUy z6n)TPxV=Ay_S}C#(N}k1u|mZ+KO5n(Fh3(z$&#d}`Swg#8nWRmT_Eos!o8O$ePlkm zBEBL)$%H#2>I-}EBh4N{$KTo7<=ycC(g`E)kL4N{+btAI$%=WnGk#=M-0={8nI9S5 zffdiJ^YHd+wORi_ZY9XvZpIma8so|AX?|L&Ce3D|CF&b$c)>5-nIU+Bz=Vh>tE&nj}Dy|=7t$NLb%1Sly3(p>feL9B5@$ALsQ2yKaBiMAAri*7#l4|Z z$J@>4?n1CfMN;e!#=x9TkGFz)l+5MFv8N9Qj#^lqy3qBCzOG)c#`mA=-{jl?M<2(4 zJ>zg_Vy{8g19$n_pPa~?#IVJ29!s4eHv$2bafE}}uraJOM;k2438)aoSC(Q#fFKN2 ztO#1F0yXkvm$+G1&T;gacB`1xnL?BX$Y#i<#+5-{Y1N~fz}w*Vsr%nJ1hUf0UUi#4 zfBe213L~K;VQAaKR=iCvj(Wl{LkJ^v_wU?qz%(|;98uqBYx}T&n0MtQ--A+y*0Svl;=0k0qae1i8I?6Q_5GExH#Kp)y}xAk z3cGta;S7LqGl$9vKK4*3fE?kH8{xtd#Fy}FnQu%lchsjzvk2BT$y>c-;m}g*wU#V; zrAaf^c^;Zp-j6XjhpXpkxzmox^+~r5atlV0L{Sl{LF$|-7rHHD4rJ3nZbtiII2`pE zDq&os+`0V&+5%w~_qf7IrXzq*JeX7vj2v?iCjnBn-BI2woh=Ci06An+5nNBrxr7bZ z85C;WQmi-IEO4^h3l7`pVPgIp=9u;97_4083=N{e zLhTd~{i6-a*0^6hb|_$E6V{eZk;pDsp_9qanNhj$homzx2Xm~PQLlCEhx*d3mY4Co zo;gW}a0pe&1{X9yJQk8rIT+2!Q5}V5HQ-`;D$H<6Daa6>dkeL5CD3acM%~rVb4lzz ze*PD^9jT%-S`{`sx zd0-UolPNJdCw+=c12ImhoEEEwSX#iewHxS6>}mjQSi7dZfr3lwT9bvUO>$``1L&2Y zYje#;^194;ALm^>yL;BXM$j_$nA^=VF1sq)(w++j)kq`KinrV(2WsQi%<7Rd~*+z(V?2Cam=NsBE%X)WC7T z%VGZc4o;-;vOfVgx37~gd++9yxD?5r!=M#Yvi7_e*3r7ttiPKBMLdf8VA5he7Dyo@ zo|f8f6B$Sb<9&U8(=3|8xE?iTgauw-g(=v-WLR8y*CdF&^mOG9n8Vx2&`sKwN7p`Z z5K?77w>;{$O8_BQ{n}ca(2a)0oZ>suigybl(X7II!19cOmmexJx_EGRm%dC8U(f0E z#`P2AIG3OjQgAXPz(@eNOYTP@gS+G8z{8=)5}45y_5%tHiKwd`2y*iTpz6r5=Gr1R z32^LqWJ@;v=P#?Gwi1*3vqz4WK?YM(--B|3xZskw`CPZV85{AdlGT=^fpa1A4f172 z8USG)I`2Oe@eKE-0%U8>UGus9J50aVuaBVu=ZM*0r~J`0Q`qy6^MJ60GK{TV~%SzdGOuOmIeYtM9yob-@>ox+5pYI2At+{wgZ6<80kUK1Io%h~dUx(TETnP0_d z)8WLNve&6A#Mn(0PR6u;4!8Dvj=GZODfx4ZSB;5fP| z2zxJwxI^+x9ys>8I+_`IFAcc|#oaONb}|45&lzr@O1LacZRE{DfJgEh(~p1w18altOiL8^c!ikYg_V>ON2VyRl$ma{Zk#>@TEJvm z6}I$5GPyAF{ar5R42H5?golqSELstPPb{VSfy8)mAf8^xGy@EteUnrn_f$LZJWVJ^ z_?A2b`CS)54rY<2;~L&0zEvh`pob=}_E*k9R1#-=z*`5@D$IdOV}y({1%V}k<4e6Q zcas9Pfrp3~iSWRU6+5rO0w`wRE6IvyiPweY=REsb0p>u9Y3f%I9-!^Tz2b4i^nhuv za_w9E71Ccc4D^|DF4(H1-xL!R(xi^LK!x%d6dG2$N}8OX^|XG~d9X4p&W>!w!aq|} zp?a|Qx{;v8e01-LmReQ%P&<;9NwKoxj%ynNzoHszkvd6HbFXwe` zPhBhKJHDVwCwrPzeHq}`hV0b z{Qnf&|0?aBU5x%+;`*(nZNJs}!*^GIpcY>(t4aJ%DK9;rOH9xxB}}tq00$)aqQIWi zmhOs{5&`Ar`we%o`|^nEe7-2mJ7I#@-{QVEgRr7Ru9wTUgkxL5RcejaD%NUl-Nu!P zTaX_~3{0(9Xq8s5ZUUl!Rm;!$>xuo;oPJ@d=?Tj=*226+uV*TYxRX>!X?A|ikjR0oGo#@k(4G+H%(|>SQPwB|R zUL+UqYDm`>h(C?nsF>*h2-ej(WC+|>yfs7GF2uvkasFI|np9y!r!`LgJ&&vE1xmy& zcRGdHK`&V}A7{3gf}{fo@`2LDI944SYZdL2OxoIUL8&W)MgoJ%saEYHKT<0p(0 z8rZ|EGkU@4R*Rr!UI zF7>0z^kFr9rQVnFx*ZF^Y$gsEdchTzjgkKCtCm1(1^Aes6#WAdm7`ODPH%VpxO~4Q zJGsshgtElRQN?o~;z;;29ssT>7V%n(vm?x5CEsaEDyDZY9E7(0s~D6j{wP0U879Fy zt&RvAYFiNbcYfnEgRRiXm&fU2(9k_{;%f6ou=cW#U9ZL$!^&?)sFrPTSG%*7`O`PkE}t5lN|8)HT}Cil}_JaZtaUsy$Li zBpdSX7q#&8h%J(>;d=#NF4ms=r9w+HyNOr|I9_Yxoyb%wFIqs#rgdh$Ssf-{yL37q zbaN=S*5HoU*vP_;(OO)ULO*1!cZQlsY9L!@Ua8bC2v&o2{A4U>!eM3TfMO@Z^dX&C*G$dZP-J~P#K@jbSF&~3XqpR(YKZ2QabxpLF!?1($v8AG_b0FFq{{dC zL-6CVtb_nPt$k#B>>Vw-N;FsozF;pGl z=bi9G*K1xcyA%3`jFQ31Pvsn5nuOC{-{Ly%#k{{YW9Z>EUSi(kpK;^W_0Z+wycm zS3S=^h4m82^K$Yk+LjLH7@56iXds3kd1HFaeu5UVWlJw>!&KX3W`lCtJj%D*h57~4 z)c(oGg^s>_e?M~UmKxqXX)JwHujDmT<$c07f-(}Q3&Sdi&Olh>sRm2f)hXjhOxAdI zWCAH9flpZb&P`vF^;4C><5j4#)fK;jX>7^B{4!c|W1|tbp30Q;!;ujq=STkYyObXl zA-8kS#f6;EdH|5`nR6Fqs6(Roh}fsA85UH}J;k?<3tDuev>}Ak*^&-+WkM&z-9Vb4 zwGOCv^Afs9Y;&DsfFi}f6+sCloBth-;lq#!h7|D$vqYuQz}}25hQ!bLL*pL2XIUIK ztiqJ!&yxXQK&jKp9du$5IZJjm?kEB=BNBp*q{qMJ7G==ZVrCh10fHTI^kSyCU}Hft zf4~UKj3mzm(z8B=8tk-E!99&pkJH_uY8CbL80#hy!d0{1B>}&l>RF5vvc47S7kc;Vgc8VGmEbNBO~|wmB<2&OLj_7{TR`nWbGngs|z%|HsA$>yeW4 z@uErW>EO9(@g8ztfWH8=inBv4siwPo4#(v!_`%j~Nfe8Pk|l*iP&eBZUFTBCh{Z=w z5f$Rm?-RFA+1RD&w)SA$6UIOotk=>Km*pUrz}obk)-HQ2RB_TeYb7e@Oe$=hAHSrO z&gasakCr#{9>RI^Bt4QlRM6~IeaqZB)(OXE$tfqpk#AdZ$`c9m7M&%J(kZ;z|1}-E>>|5*QTucG-Cvt zi46h=S{`L!(;o^*+aWdR&Ja72zTTGz6WgB_q8(uai_WH>#MIWE$-W@NAggmtbNV{O zAcC2cy{we?Ji|XIZN(v z$h~IoM|QQ{MU<1rEeYqx2}4H8gOP`Cw$8G7*f*pS0zz(mciz2e@9c$lZnnJ;n?Wm13Oe=UPPxh~ zDc;;umyXKiO)mMy$WLxwj`2&ET=^vQiA|>r{15eBKH4g8sy+Anxb?zGL@~+4kHZ`O zzNr<}UbPNsHKL~^^wqSeM}h*Wqo@xi{grM>AdDu6mGyfb{7*~oJ$04r+NP(N!5Y0? z9N`LbXnPR6xs_wmZqh_1f-n^Fv%&=iw4`z2f&&`IbV97tzU5Ahm7;++s}2*NbZ^QF zSOFuD7=%&C*C!hN$0qGEpg9g$1QZ#dk>*0(g$e;}d%HeV`KGwU)qqM#Czf{=_ zb_;!^v9f7oFeuf=8mWYjb0yW9aCW_1tJ`X*j$jUHccXsBC`Z&BHsdlYDa(O*c!5E9 zlPTqUMN{#ZZO!7=SdC^4L@g~+eaB)pm3`u7>VAqzleQ7l{>vaYkCa-DAA53Xfqnh0 zTPn%E_w^Ij4-3Ca(gXFxz<|pI6X(m+bw-D@n_RL?XRR_-uxWAfuir>%wc{0wPSBeDy=fxQt$2cc2&x zA@Ks^gOizme)<@pzxL_VvggD=2-_X?*gcbZ(sXxVu)0ho^$v5kP{zP(zaHj#lc<`i zM~VsDZ|9ou_BRM+#>8)ZJ<=wd39Mj*R~Q^6CZ;1G1AyJH#NGS$LL=W@VE|q z4XsT_joJE)wRBC=E7^AKm9=WGSXeAk{NZt+g~KwJa?4)1GBiWgWwq*KkP0Y!d#RWy zWqrp@8$Y1v(oP?&JV(tVJhhf>NOIyTy&Y4Tj?~qk0J+NX!LnN z*45F=KX8T}IU{5xh6;ZvRa5!<1oVqB2;X^sanGdM6yFi?S5GZR&Vk5mNH1?FfS#<1 z^7d5Ea-Pk3{K*jRH9R!xF^ZmDN8+0?>wBQIX`!22T|YJ%SBoQ`m=NuKd`+NKqy^5S-GK4he=kgjzC=4c}KaDa|*fs_L5CCxd{Pw2H%+ zFWN7|NB3>7NM3jGbZ`r)$6p44bkw?C$YXkfdir!}t!9@&t4AL~I36*h;fFdDlyv1i znF^FU2hU(uBKtBql=^GIljH65D9bPAn=8|s*{r3P|8KF?e*!|-p_~K=lL^2 z+u(Np@c!`4_$lHt%^xi5#666!t1?PdahJqgrqIfaSB(Hv=dmj4ek$uYdt$BJESb|Q7W_AZ zOwvAnf&t2)%vR=ltfCD>36NxM>5FdK@mA$7-WjK@BLXn?0tvyZXEFt`F(NV#&n?^ zII&0#I4~S;1E>hveREfpTT^8(KRR<`Q;&Jt#6NlRZsB@0?kN4eJZZ*&<~$7Zp;9G~ zI3;f@EL|08UMvEP!rkJTE7lVw*vl^84`E?fQb`03_5>sM8kw#Rc*g&Ra;?%}5MO&Jgo>@^;qvc`&xZdhOV1*juDCQP;@@o&y zBJH?eKZYv{MkNmDkFkA&_)CEk2xM#^(bgc9NmeKsR=gXykIMz3}g#2NuwjdQ~^y}Hamk_^xoD#}u%da*q(r=7X*#i^uBNv}ASvl|hr(mNN6WSju@jOQyQU;lYztu*vb8 zZVS?=UQ3B`Srcq1$FhyEpt`upQo!;00BgM+edUHDSrbT<_qjl9Qrd(w|A*MY)Bf!l zO5o#+OV!nFC)ZE8Qo|-6d;=i!-fJsq&M>!;k9M7GFI5g3cG3Rg+}XxTJVu)OkfHRk z#db%;Qf%$r5L_Y@p5^WG*U8>K&6r{#*x5yVj0T?3mRV{xJ!ZR|Dgw-Q=@AQ^V^VdX z9EHSTO9l*YMBJQY^B+!wiv%}6LL1`o*e2lfmC-ju1=>&PA6mkKCnQlmLal-1w^@`DApbTb!uGBF zrufVgq_(eAlA+XJ==X)YxzH8~+NdK~MzQEqm<~+Id);BZD#3)0VT~F==G)7`(omST z2_cm39*Z;oKtd}JK;(gWBzV7|W{~L^JXx@lkB7660dKIB2#$a*^4XS(Y7IHqV!24? zyNOhsct&f1{)#lh-s;Xb)0GTwxgKa03ZytLS*PF>!Vnw}u?dRRAC+7Px~9om-g*Ml zCNDL+8AgqR;*_icmZb|pfKxPmaH2|!5m1bvNn_}}ogUtaOpsw)$rl#c$GBGEPUIJ8^Dn*9ml7mu%HKo_Za>N4z_bL7!a30|8$Os{=*z@t>9-&nUm& zxjUnc24nlW$V!-rMpcZ&Bom_^p|i%JHq-34vQpzvVvaZZK^01Y!s!T6u!!mf1~h_` zAlx8!RQyvloz{Y`sQpmhHB!=7(fnA+oqaAZGC(CI92|;ZR<4pRvI;8Jhz=y*JsLMT$o$3(up-XyGDz@)ETfO1B7OL}%g=1I}w zwH9QAmsWjYLyvinS6DqJ{nUThE2K8=0tGVh0vR&+0uhp=PsT+GF20!^M$Q7Y}~2OHlZQ^!u+YDRY!*S$ZJfBT zSG`j566Prx&~-T@Fs26{O*R-KOq`F={DfZZ@Bm*t->xj%sIJt0N(A7!`vj*=imwQB za4l38ah;KD0cF0}T|V5liK+42bn*4@!L2vJ^2}s2x_04r27i8xJa4v| zOX36qb$Rgh_m-~tc`Cf;AHu;f%Ia&w4T!M z5xWSxW=)b3dDn~)1`*fuWpUo_xq zv#mjtq&1(3Q6N?>sZsFmc&)*WkiR~Jy*)X1?5s&d-RH?F%q~+d zet1+-_K#2>lx6Hk`Iw|ND3uM-w%GzeWoTzL^}XeDB42HV&Dhi0*tPHa?PXVD(*+n- zrs1L^^Im0}?=uuIl#B!9M6^?nM-wFZ{FDg=(+|tx++=OIdJAWpY&|Wl+m3@smfU2XP6wS+;13dUR&&rg z$RRBf?6AhjFhyf^%gRB%h-Xt_FOk(iL%`0zfAolX5&c1!g2zqW(gya-Dz7ZK6pVc_ z4Vi%L>SsG3oGoqpf#fJJnkj)EC1z$!N%*0<7vSL#9q(IoogM$YPgo$R6Rv02@}AKz zRK#lA+9r=r#YApF$bru(2&>-vlld4+UXBiiqjB8c>xlh$04IrbiuyYAP~7%(WL>lO z1bz2o)<%}mUdJWMRkN4i8#qA zWadQS_ENy^m1nWNq#!3cBfB*~SRT@~`Y#iY61x)P3_-q81xAnC2So%1_qn1-fgPBa zfJ;mzEy8H<8YR*a9zF-8v-8qu;iHGD7VNh(1fIL=V62Se@f_9EsaSfb!usXov|Th3 z+yy%-I7#dWn5gUr!lX}#?xX&2Jf3llBo1qyjPh#X1@oS31u5v>q>3|2o|Hx|NjGHn zb~O4J9R7d8S>LCdi{WS))}wi&lX9ZlkjKzOSt66Lj)5iLiBqBdwJG>QtPAR|)-lXI zPbd9^cciQfiHXWjK+M`~AXn}|?$Ee8ExFR6r`%s1xk9MrB-J$guraa?+8)2^p1N8~ z=#Ypm0CMV1NbeAdey=1#X_n3koup2W>;gB)S(N1jD4Hj4K8K zpoPI47Z}G-C@I7>CY?nMApbj;kad}b5fxK~#ZjgO9fxoz8fpKaT=ZQHhO+qP}nwr$%wb#8S;cis0>-}(Xb!-`muGv;_QNB%(? z-6`z-JZ=fIcx+s$v`|4cDK`<6IsnD_nS&^Z6V&JY*V5>RO^n}>2Zec5x1~=02U9H? zF{x<`2=GhVb&i4*ry#=eTP3K%7B6F+MZ3Hb=RcEVu)XrtcAL7PCSH&2MyLC%o zIXAPFt+Tn&ul16o<07Ja5>!J4wvE4hMhU+7&URqE$xT{>^iwr8t4Yq&&jz#yTDt-i zfYX4IwPv|#7M_B$O<%_Szz7!=CVt=Y+ThvEf^c2nL^N4Pg**BY&gHMBPvdHl(8b%} z%$+sRaUTvZPIf<=#6eXDg&OhC=J1;f?7I1Tn>B5v4=Z|J>`XE<84)Tny04Au0sE*> z@pl+zJ2Ds1G$*2jp6wjV;n7q-_eC+Yx78HgT4#9lBW@;>bGM&cnd0!S4Y$P7FpB|(Vmu$+>3 z0SgefEzLkBS=iNgUmcWa0g|zzH;Y#_uAjeIG==3Bmn!G*08+zB?Nd0)a*ZYI< zZ*PwFaq3#$N7osls*W=DV5tr`c)*`t>1}J>wKrxaAyCyx zy=YiOD-LQubxWaB2^sbk@v*@nojn>L(NXza7N}7GkN30Px(I>giC`Tn=A|^vw^_KJ z2K-I8Eqsk;&^nWDfb***CF_1Ldw}JsVnhMY1{%PRKp`)k&5eqP3IJ{@OWMU(Cojvd ztcjBS_m{&gGsilxq0ltDa*{Zx6HP>i&aoA1ubKC$cRck>O z)M7!fIwH76auhV*n(rUvOnw?FP9zxO=x)cKr3c3Fgb%GntlG|muv`pKcgX& z%~K{KeV7dSJUpfeT|{i~=RB2e*;9DRjleTpa{-ZQ}xWKiL>kI>=Hm7ptD z!bFr&v}l%+W*xIgv^w^6v|m@oG|ncM3!wGb&AZ(6BLC(BFB=S&v!F*s&}v!}e#~fhUq=#gFSOA%MK3F#5fG824v(dp#w6r6CYAO>|0n6pRkI)&(I5lO=A-42xr2@}^SfS%0)_fhj6LV9wvFWISK2hUhUv z6uwch%MzWZ_jrE&Vq~14EFdM)RQ_p={z3d`#7VJA(eC$HN=N^5xhkK<%hJAvR?~^B z!GH3se|Vzle;;g`Tx3V8vKK)ezN{AD=q=xZWCkVWd%+rQCih7$6gv5XB@ZZ-$Md?Z ztE}B4|6bXR!s7pMyh-ojfgmOJZoyw6fRD}IDTAUR}|cHjDUKFsQLC7)k(U_pb+wB@*=++Efhep@1utB zTlB{d=HLhQ#bR(P>L%;-Ex@OMh#opWoBWI zIZj5_ZcBkmNNU*(Ebnm$eLj)%Tc`5qYB0i#Y7p~=V+ip?>OiUPc&ZCa5{=Jg&4lVzlT@oE{rr~ZK>?RG zc+2O7la5Z5?V3W+E03KrUEPz+lz`~4aDOn)m!Ytg0R2OX3_(?5iIp)G}3_0ODK@9Y132eri(#L^+$zVUbQB7lF2Fjk#J~mhPbu_e?l9>Wf`z zJWfClVujV3b7u041S|}_mz5xNU{6FFpJjoiP^J3~KN&;LB@c2n3TjVxu4 z`#LsPCL~zeE3Nqnsuklw$=5US_LujX;n6TpOw0b+JXM z9{Y)#0N--;1-f0L%8z4h4|@ZZ($wlW876Hdnlmcd)(&6AsN9o_#-7yIB~uxzg!EUVS@w?Vznv5o+)= z?b+hMXqOv+E0L@yBhMg#qX)i@7w$xJP~mKkLu%6G;ePc z{N3$v`1N#$>D^zqg+Hv`cbi`&@c8cS?AWVz0}>{A-URB%qvq(k#2)8_q`R=b1zJv@ zLzJX}*m1b?Eensi*F1th?CE69je+(S*iqVTyXW zm{hVxZRSg=`ym$F4E#gskc34|9nR39By~H=d%P34_wsw#q(ns+O`b=b$d_O)#DBJ8;varD|vVTzJgjr7CisaKCMpggz=$C-HM&$jl3KBz#T=-cr%p-n|*(2 ze#>Hwz8R*TN|pbuRJz)YtH7W^r25XXSVeMCFIenH*W4k%{Y4= z1F=1XfNH}n9?zK7?M9+2hc8{C1v0Cii@m17qqIq@O!IuJD^_Ff&e %UFlr9m4Xz2o$DMdi35L_tnw%iyuhZR@Fo zIvszfQGqDl0#%_edbVdvxfHxC?wec!rEK#j0UFCK&Of{i;hd^_R9praO@c-UA(H#A zYlFghOq`U3-7nuBk?p!7k{(W*n*zt3b5HlHM0+YjI&;{Bbh3$g!o_PYv*KWTveE^+%{qK4)F-v#^ZEPl6q#Vz}#sEhJmo{UUjY2bw z#2tGAE5@DgpKHKEO{?G0&lSXWX#cmhbaDN7`F)4E~NRXo$bY9a2#AgVur6P#L zwlWFjC2*BVyw~$Un8-#gt(GoC;kU$#Vw*Xr0@U_1*x6bWI5sU>G$p)=bio6$X3R`= z-a+hX*FH;*UFq(w^gs;Kp$R*ahGDDaVojPzPvaP>KxZ=p$fTYwmubgDR@-9N)M)xn zF0F#WtlwiwXTI0ZiU*l?n-G_YfL!x2TGutvF{n&#q9J23=t_qhR;Z|YaIJkP|DNh( zlvc|ftKl{hGq)xn%e)4GrnZ{2U9r#MlqXJc*fZZ!sw-@t&xK#vJud&N?+)Hk?((TB z(f(5;TZbfXiVWI%-Y!kzKJ+OM5>0Y7>k~Wd67k=KCk@cxyOUtp{25%5vh|}0ktsmO-kIsPiR(~I*8Xuo3 zr??8Itg`R@_zo*X9TD@}E1|WG3cIrNx#aVy{^cEwPb2jAEW}$=;1L(Mu>eW8Hr##P zg9r8O!Z6(}|Gupt*nz(U?rkvuyVW~Gr2t_>#?xH=&6M)n*l?WWyOn&|%#4k`9lfuOZgssWAUx(pU^LEFrz$qDrrrOA^OlFMZqX$G^nE_(X>==zhaair@ z*L^npYAsg?jdZXzYWuyt&IpL&H_fX}p>_{d)p*msIx0f+Y8GSI;4{%+Pb$g8jPIh76E?{@>ZX}w-yhV7I`|DYfuxjc5_;fES6YCh}3qQbN^<`kN>V_ ztp?y(E!ZJH&d>M2IJFtfzQG^R~{;iwC;?fM|4mY3{V#mRNC^F)xFEVh!&k&r)e zHhB{lw1|zkGWNt6@ElQy=3CyVLlG0^eL#+A(L-a&bdz~+@jVec-+Hp{?b!H{bL{TK z+)H62zxJb9G_He~W+#B$I3sXq*Dqi~aX@0aUhz$(Dd;ud5KB_IH>q=vmy3%e_Tk{P z=hjdXIH*=tWI$D=MXBotH_4=~p4AUlI`^Zy9H6h972d5COk3PWb{?yrX*-TAEDC=Q zv2P!v3|sH-l_x9=dGKDmx^hm7;hOT*L~3HsvMc7}hQqrTWYs1?<#~({R^Wo<<1qMJ zV0QT)!U5T7?a^+kg_9EdqvWK9v7p38iHD(EUxk<5z*Tu|E(5?H;=Xnm#L4WsFpPcP z*H!Eic3qZJHtS*|)saekE(9#*?TXK|O-kD_qwA6Ip!A5

&k{$GZesiPJ7a29-}r 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

jRG&zlJ&;@kg!}0d&OyKEA829A8bDu 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

XLsl<%o{4RcvYtMNUv$%o{VAz#addnix?; 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

yj$Q1@?)n00Ll(a&%&X{icYWb4j+4Q6hj(U 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

VdV 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�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