{ "info": { "author": "Mark Jarvis", "author_email": "", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering :: Information Analysis", "Topic :: Software Development :: Libraries", "Topic :: Utilities" ], "description": "# pythondcs\n\nThe pythondcs module provides a convenient way to allow a Python application to access data within a [Coherent Research](http://coherent-research.co.uk/) DCS v3+ remote metering server via its built in web API using json formatting. This may be to download meter reading data to validate invoices, big data analytics, or simply to dump into a file for consumption by some other system. Whatever the purpose, this module handles the link to DCS and returns data in standard Python data types, including those found within the standard [datetime](https://docs.python.org/3/library/datetime.html) library.\n\n## Getting Started\n\nThis module is written in pure Python and should work on Python 3.6 and up.\n\n### Prerequisites\n\nYou must of course have access to a Coherent DCS server system and have a valid username and password to access it. It's assumed that you are familiar with how meter data is structured within DCS and understand the concepts and terminology used. If this is not the case, please refer to the DCS User Guide for your server, or speak to your DCS System Administrator.\n\nThe only external module required is the [`python-requests`](http://docs.python-requests.org/) library. If installing `pyhondcs` via pip, this will be installed for you.\n\nFor efficient handling of larger data sets, you can optionally use the [`ijson`](https://github.com/isagalaev/ijson) which is recommended if you envisage accessing large amounts of data in each transaction (such as years of halfhourly data at a time) as this will provide memory efficient iterators instead of lists. However, you may choose to omit the `ijson` module if you wish if you only plan to grab small amounts of data in each transaction, or if you don't mind the memory burden of very large lists with nested dictionaries. The `ijson` module is also availble via pip.\n\n### Installing\n\nThe `pythondcs` package is available via pip, which will also install the `requests` prerequisite for you if you do not already have this.\n\nAt a command line (not in python), such as Windows cmd, or Linux/Mac shell/terminal:\n\n```\npip install pythondcs\n```\n\nIf you wish to ensure you have the latest version, then run `pip install --upgrade pythondcs` instead.\n\nOnce installed, `pythondcs` should be importable from within a python interpreter and available for use in your scripts.\n\n```\nimport pythondcs\n```\n\nYou are now ready to connect to a DCS server.\n\n## Usage\n\nThe DCSSession class provides the methods/functions which allow you to login a session and obtain data. The methods provided are essentially just wrappers for the DCS Web API. For further details, please see the official [Coherent Research DCS Web API specification](https://www.coherent-research.co.uk/support/extras/dcsapispec/).\n\n### Logging in to a session\n\nTo create a DCS Session, create an object using the URL to your server, and provide your username and password. The URL to use would be the same as for the normal user interface.\n```\ndcs = pythondcs.DCSSession(\"https://url-of-dcs/\", \"myUsername\", \"MySuperSecurePassword\")\n```\nThis will then return: `Successfully logged in to DCS as 'myUsername' with Viewer privileges`\n\nThis session object will obtain and store your authentication cookie for the lifetime of the object.\n\nYou may alternatively just provide the URL which will create an un-authenticated DCSSession object and the `login` method can be used directly later.\n```\ndcs = pythondcs.DCSSession(\"https://url-of-dcs/\")\ndcs.login(\"myUsername\", \"MySuperSecurePassword\")\n```\n\n### Getting a list of Meters or Virtual Meters\n\nGetting a list of meters or virtual meters is as simple as a call to the `get_meters` or `get_vms` method\n\nMeters:\nIt is possible to fetch a list of all meters, or one specific one.\n```\nlistofallmeters = dcs.get_meters()\nsinglemeter = dcs.get_meters(405)\n```\nThe data returned in both cases will look much like the pretty representation here:\n```\n[\n {\n \"connectionMethod\": \"tcp\",\n \"deviceId\": \"\",\n \"id\": 405,\n \"name\": \"Sample meter\",\n \"remoteAddress\": \"10.8.222.99:5000\",\n \"serialNumber\": \"R1100729\",\n \"status\": \"online\",\n \"registers\": [\n {\n \"address\": \"130\",\n \"id\": 733,\n \"isInstantaneous\": false,\n \"name\": \"Active Energy (import)\",\n \"scaleFactor\": \"\",\n \"defaultScaleFactor\": \"1\",\n \"unit\": \"kWh\"\n }\n ],\n },\n ...\n ...\n}]\n```\n\nVirtual Meters:\nIt is possible to fetch a list of all virtual meters, or one specific one.\n```\nlistofallvms = dcs.get_vms()\nsinglevm = dcs.get_vms(9)\n```\nThe data returned in both cases will look much like the pretty representation here:\n```\n[\n {\n \"id\": 9,\n \"name\": \"Sample Virtual Meter\",\n \"expression\": \"R1+R2+R3\",\n \"decimalPlaces\": 2, \n \"isInstantaneous\": false,\n \"unit\": \"kWh\",\n \"registerAliases\": [\n {\n \"alias\": \"R1\",\n \"registerId\": 1156,\n \"registerName\": \"\"\n },\n {\n \"alias\": \"R2\",\n \"registerId\": 1164,\n \"registerName\": \"\"\n },\n {\n \"alias\": \"R3\",\n \"registerId\": 1168,\n \"registerName\": \"\"\n }\n ]\n },\n ...\n ...\n}]\n```\nFor more details on the output, please see the [DCS Web API Spec](https://www.coherent-research.co.uk/support/extras/dcsapispec/#meters-and-virtual-meters-get-meters).\n\nThe important ID numbers you'll want for getting readings are the **id** of the **registers** under the Meters, such as _130_ in the example above, and ***NOT*** 405, or the **virtual meters** itself which is _9_ in the example above. These numbers can also be found within the DCS front end interface (the one for humans!) from the \"Registers\" tab when viewing meter data, or directly from the list of Virtual Meters Be sure you don't use the Meter ID by accident.\n\n### Getting Readings Data\n\nThis is likely the most important feature and the reason you are using this module.\nThe same method has been provided to access Register Data and Virtual Meter Data even though the underlying API calls are different. The parameters are essentially the same and result set have the same structure and so these have a single method within this module for convenience and consistency.\n\n```\nresults = dcs.get_readings(id, isVirtual, start, end, decimalPlaces, calibrated, interpolated, useLocalTime, integrationPeriod, source, iterator)\n```\nThe only required item is **id**. The other options are optional and relate to options also available on the Web front end of DCS.\n- **id** - of the register (*not* the meter.) or virtual meter (Required).\n- **isVirtual** - True if id refers to a virtual meter ID, otherwise False for a register. (Optional, default False)\n- **start** - a python [datetime](https://docs.python.org/3/library/datetime.html#datetime-objects) or [date](https://docs.python.org/3/library/datetime.html#date-objects) object (Optional, default omitted), such as `datetime.date(2019,1,1)`. If ommitted, the default date used is defined by the server\n- **end** - a datetime or date object (Optional, default ommitted), as above. If ommitted, the default date used is defined by the server.\n- **decimalPlaces** - of the returned data 0-15 (Optional, default 15)\n- **calibrated** - totalValues to be calibrated (Optional, default True)\n- **interpolated** - gaps to be linearly filled (Optional, default True)\n- **useLocalTime** - for timestamps (Optional, default False for UTC). Returned data will be timezone aware regardless.\n- **integrationPeriod** - Defaults to \"halfHour\", or \"hour\", \"day\", \"week\", \"month\" (Optional)\n- **source** - Defaults to \"automatic\", or \"manual\" \"merged\" (Optional)\n- **iterator** - False (default) give the results as one large list, True will return a generator where each reading can be consumed one at a time (Recommended)\n\nIf the start and end dates are omitted, the server defaults are used, which is generally a start date of today with an end date 1 day later. In almost all cases, these would be specified.\n\nThe results will be structures as follows:\n```\n[\n {\n \"id\": 26167735,\n \"startTime\": datetime.datetime(2019, 2, 1, 0, 0, tzinfo=datetime.timezone.utc),\n \"duration\": 30,\n \"totalValue\": 10161.5,\n \"periodValue\": 11,\n \"isGenerated\": false,\n \"isInterpolated\": false\n },\n {\n \"id\": 26167736,\n \"startTime\": datetime.datetime(2019, 2, 1, 0, 30, tzinfo=datetime.timezone.utc),\n \"duration\": 30,\n \"totalValue\": 10172.5,\n \"periodValue\": 5.3,\n \"isGenerated\": false,\n \"isInterpolated\": false\n },\n ...\n ...\n]\n```\nIf the **iterator** option is used when `ijson` is installed, each dictionary element within what would otherwise be the \"list\" will be yielded by a generator function so you may embed this into a `for` loop:\n```\nfor item in results:\n # Do something with each item individually as they arrive\n```\n\nFor the **iterator** option to work, the `ijson` module must be available, otherwise this has no effect.\nUsing an iterator (`iterator=True` with `ijson` module) will yield one reading at a time which may be more memory efficient for extremely large data sets (i.e. multiple years of half hourly data etc.), particularly if, for example, you just want to calculate an average without retaining all the data. However, if memory usage is not a concern or you need to retain the data for more complicated manipulations, then `iterator=False` (default) will simply return one single list of reads. This may potentially be very large. In both cases, each element of the list or iterator will consist of a dictionary as received by the server with all numbers as integers or floats, and timestamps as timezone aware datetime objects.\n\nFor more information on the meaning of each item, particularly the `isGenerated` and `isInterpolated` flags, please refer back to the [DCS Web API Spec](https://www.coherent-research.co.uk/support/extras/dcsapispec/#metered-data-get-register-readings), however if you are familiar with the DCS web front end, you'll probably have some familiarity with these fields. Generally the `id` field can be discarded.\n\n### Logout\n\nWhen you have finished, it's good practice to logout of the session so as not to leave a dormant/orphaned authenticated session running on the server, or authentication cookies stored within your application memory. You need not do this if you are using the DCSSession object as a context manager within a `with` block.\n```\ndcs.logout()\n```\nThe logout method will not delete the DCSSession object and the `login` method may be used straight after. This can be used to login again or change credentials during execution.\n\n### Usage as a Context Manager\n\nThe DCSSession class can be used as a context manager for more compact code with the real work being done within the `with` block. This will handle logging in and out for you as the block is entered and exited.\nExample:\n\n```\nwith pythondcs.DCSSession(\"https://url-of-dcs/\", \"myUsername\", \"MySuperSecurePassword\") as dcs:\n # Do stuff with dcs\n # Do more stuff dcs\n```\n\n## Basic Example\n```\nimport datetime\nimport pythondcs\ndcs = pythondcs.DCSSession(\"https://url-of-dcs/\", \"myUsername\", \"MySuperSecurePassword\")\nlistofreadings = dcs.get_readings(123, start=datetime.date(2019,1,1), end=datetime.date(2019,1,31))\ndcs.logout()\n```\nIn this simple example, the appropriate modules are imported, including `datetime` which will ensure all dates and times are correctly handled. The script then logs in, and downloads readings for register ID 123 for January 2019. This will default to calibrated, interpolated halfhourly automatic data up to 15 decimal places long given in UTC/GMT as would be the default as these settings are ommitted. The session is then logged out.\n\n## Elaborate Example\n```\nfrom datetime import date\nfrom pythondcs import DCSSession\nwith DCSSession(\"https://url-of-dcs/\", \"myUsername\", \"MySuperSecurePassword\") as dcs:\n listofvms = dcs.get_vms()\n for vm in listofvms:\n if vm[\"name\"] == \"Virtual Meter of Interest\"\n idofinterest = vm[\"id\"]\n break\n maxdemand = max(item[\"periodValue\"] for item in dcs.get_readings(idofinterest, True, date(2019,1,1), date.today(), iterator=True))\n```\nIn this example, slightly more condensed namespaces are used, and a context manager is used to create an authenticated session which is then used to get a list of all virtual meters. This list (containing dictionaries) is then looped through to search for the first one with the name `\"Virtual Meter of Interest\"` (assuming at least one exists on the server) at which point the ID number is retained and loop broken. This is then used to efficiently (using a generator comprehension with `iterator=True` option) find the maximum halfhour demand value for that virtual meter between new year and the current day. The authenticated session is then automatically logged out upon leaving the `with` block.\n\n### Exceptions\n\nAny exceptions raised by the underlying API call will be propagated to the caller and so it is for the higher level application to deal with them. This is most likely to be from providing an invalid id number when getting readings for example. The only place this does not happen is with logging in where the error message from the server will be returned, or logging out where exceptions are simply ignored. If an exception occurs during login (i.e. invalid credentials), the DCSSession object will still be provided in an un-authenticated state where the `login` method can be called again directly.\n\n### Concurrent Transactions\n\nThis module has not specifically been designed to be thread-safe, but will probably work in multi-threaded environments just fine. There is however a thread-lock which deliberately limits each instance of a DCSSession object to a single concurrent transaction at a time (irrespective of number of threads which may be trying to work with it). This is primarily to protect the DCS server itself from being overwhelmed with concurrent transactions. However there is no limit to the rate at which consecutive transactions can occur. Therefore care must be taken not to overwhelm the server with numerous small but fast requests - including invalid ones raising errors. Concurrent transactions are still possible with multiple DCSSession objects or, of course, multi-process environments.\n\n### Other functions\n\nAdditional functions are available but they are not documented in the [DCS Web API Spec](https://www.coherent-research.co.uk/support/extras/dcsapispec/) and so they are not fully documented here as they are not officially supported for use by third parties. The functions provided have been reversed engineered from analysis of how the front end user interface works and so they are to be used at your own risk and their behaviour may change at any time. These are available within the `pythondcspro` module which is supplied as part of this project. This module essentially duplicates the standard functionality and so can be used in place of `pythondcs`, but with additional features, some of which can modify the DCS database and so are to be used at your own risk. Please see the source code inline comments within this file for further details.\n\n## Author\n\n**Mark Jarvis** - [LinkedIn](https://www.linkedin.com/in/marksjarvis/) | [GitHub](https://github.com/jarvisms) | [PyPi](https://pypi.org/user/jarvism/)\n\nI'm employed by [University of Warwick Estates Office, Energy & Sustainability Team](https://warwick.ac.uk/about/environment) as a Sustainability Engineer and as part of this role I am responsible for managing the University's several thousand meters and remote metering infrastructure based on [Coherent Research's](http://coherent-research.co.uk/) equipment and DCS Software platform. While this module will inevitably be used within my work to cleanse and analyse data, and may benefit other users within or collaborating with the University for research projects, this module was written exclusively as a personal project since I'm not employed as a software developer!\n\n## Contributions & Feature requests\n\nFor bugs, or feature requests, please contact me via GitHub or raise an [issue](https://github.com/jarvisms/pythondcs/issues).\n\n## License\n\nThis project is licensed under the GNU General Public License v3.0 - see the [LICENSE](https://github.com/jarvisms/pythondcs/blob/master/LICENSE) file for details\n\n## Acknowledgements\n\n* Thanks to [Coherent Research](http://coherent-research.co.uk/) for documentation and on going technical support.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/jarvisms/pythondcs", "keywords": "energy,metering,coherent,DCS", "license": "GPLv3", "maintainer": "", "maintainer_email": "", "name": "pythondcs", "package_url": "https://pypi.org/project/pythondcs/", "platform": "", "project_url": "https://pypi.org/project/pythondcs/", "project_urls": { "Homepage": "https://github.com/jarvisms/pythondcs" }, "release_url": "https://pypi.org/project/pythondcs/0.1.1/", "requires_dist": [ "requests" ], "requires_python": ">=3.6", "summary": "Python Module for interfacing with the Coherent Research DCS v3+ remote metering server", "version": "0.1.1" }, "last_serial": 5534906, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "5426082eb6c1ca4fe70bb7e909c6cbf0", "sha256": "698b3b6ca5a14d0f1e5ae7c77e44ddf5f4fc35052551ad712f76571fb5b2e754" }, "downloads": -1, "filename": "pythondcs-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "5426082eb6c1ca4fe70bb7e909c6cbf0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 26216, "upload_time": "2019-06-17T06:12:18", "url": "https://files.pythonhosted.org/packages/40/9f/37a5050b4129b6e0a7c66c5777227fc22cd7e18d7a37b21bab8ce8419068/pythondcs-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "491d3f7e04d61fa1ca47a237a27c10ee", "sha256": "75e0f739412b27bde6c82958d9ec866bf65a3b13076904349a2fe8e1db1f6367" }, "downloads": -1, "filename": "pythondcs-0.1.0.tar.gz", "has_sig": false, "md5_digest": "491d3f7e04d61fa1ca47a237a27c10ee", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14340, "upload_time": "2019-06-17T06:12:20", "url": "https://files.pythonhosted.org/packages/03/13/81585413275aa4e01da7d8be5d401a174567a02ee08a19e9aa2f41c95cca/pythondcs-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "67f675aa664f157b10742734ffbb2982", "sha256": "ef24c7d2de1d86d70241b1a066acb15c310f47c41af46bfb9dfbf78b8baa3b35" }, "downloads": -1, "filename": "pythondcs-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "67f675aa664f157b10742734ffbb2982", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 28261, "upload_time": "2019-07-15T13:19:33", "url": "https://files.pythonhosted.org/packages/2c/b2/b465ef3c70f9b0138c802732485e6571d2b1a2557167f1d13eb3c894655d/pythondcs-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0b1ed2ba039acf56cd56245eef4e78fb", "sha256": "c1e5a4dcacbe0c2409c89731e74d0eef7cda6a35b07ec79bd928ca1755b29075" }, "downloads": -1, "filename": "pythondcs-0.1.1.tar.gz", "has_sig": false, "md5_digest": "0b1ed2ba039acf56cd56245eef4e78fb", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16497, "upload_time": "2019-07-15T13:19:35", "url": "https://files.pythonhosted.org/packages/c0/1a/16332fce2e60a370a218618d58bcacfdf6dbf0a2646943938a2fc7556b9a/pythondcs-0.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "67f675aa664f157b10742734ffbb2982", "sha256": "ef24c7d2de1d86d70241b1a066acb15c310f47c41af46bfb9dfbf78b8baa3b35" }, "downloads": -1, "filename": "pythondcs-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "67f675aa664f157b10742734ffbb2982", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 28261, "upload_time": "2019-07-15T13:19:33", "url": "https://files.pythonhosted.org/packages/2c/b2/b465ef3c70f9b0138c802732485e6571d2b1a2557167f1d13eb3c894655d/pythondcs-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0b1ed2ba039acf56cd56245eef4e78fb", "sha256": "c1e5a4dcacbe0c2409c89731e74d0eef7cda6a35b07ec79bd928ca1755b29075" }, "downloads": -1, "filename": "pythondcs-0.1.1.tar.gz", "has_sig": false, "md5_digest": "0b1ed2ba039acf56cd56245eef4e78fb", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16497, "upload_time": "2019-07-15T13:19:35", "url": "https://files.pythonhosted.org/packages/c0/1a/16332fce2e60a370a218618d58bcacfdf6dbf0a2646943938a2fc7556b9a/pythondcs-0.1.1.tar.gz" } ] }