Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates for modular RC. #14

Merged
merged 31 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
09aa93d
Added (monolithic) documentation for Fake Driver.
davidraker Mar 14, 2023
1bf6177
Modular updates to documentation.
davidraker Mar 17, 2023
ee8d67d
Modular updates to documentation.
davidraker Mar 17, 2023
9f6bd15
Merge remote-tracking branch 'origin/docs' into docs
davidraker Mar 17, 2023
5338963
Corrections to links in documentation.
davidraker Mar 17, 2023
d3551e3
Additional documentation tweaks.
davidraker Mar 17, 2023
d8d00d0
Corrected missed installation command in modular documentation udpates.
davidraker Mar 17, 2023
43d5b8e
Updated documentation, README, and dependencies.
davidraker May 1, 2023
f8273f5
Updated run-tests workflow to current os and python versions.
davidraker May 1, 2023
a86e655
README fixes.
davidraker May 5, 2023
cf39118
Merge branch 'develop' into docs
davidraker Oct 2, 2023
d7d3f61
Merge remote-tracking branch 'origin/docs' into docs
davidraker Oct 2, 2023
3151862
Configuration rework for driver service work.
davidraker May 15, 2024
a6259c0
Update to support new Base Driver design and Pydantic configurations.
davidraker Sep 14, 2024
71d43c7
Added get_multiple_points method. This just calls super, but it is no…
davidraker Sep 19, 2024
fa6f3a5
Added optional remote_id to the Fake Driver interface.
davidraker Sep 22, 2024
3b4d6a0
Refactored create_registers into create_register (moved loop up to ca…
davidraker Sep 25, 2024
8d64cae
Updates for revised BaseInterface.
davidraker Sep 26, 2024
b3788a8
Implemented updates to BasicRevert.
davidraker Sep 30, 2024
51b0896
pyproject update
davidraker Sep 30, 2024
9451e88
Base Driver is no longer a BasicAgent.
davidraker Oct 1, 2024
ae8307e
Fix to super calls in Fake Interface.
davidraker Oct 1, 2024
ea84cb1
pyproject update
davidraker Oct 1, 2024
e63194f
Remove volttron-testing from pyproject.toml.
davidraker Oct 2, 2024
7cb1674
Removed local paths from pyproject.toml.
davidraker Oct 3, 2024
bd8f223
Merge remote-tracking branch 'origin/docs' into driver_service_work
davidraker Oct 3, 2024
44643f6
Correct property use in _get.
davidraker Oct 3, 2024
a59951a
updated link to listener agent
riley206 Oct 3, 2024
b4e9894
Merge pull request #1 from riley206/driver_service_work
davidraker Oct 3, 2024
61e1b3d
Updated pip to poetry in readme.
davidraker Oct 3, 2024
24f00b4
Readme updates
davidraker Oct 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 10 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
# VOLTTRON Fake Driver Interface

![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)
![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)
![Passing?](https://github.com/eclipse-volttron/volttron-lib-fake-driver/actions/workflows/run-tests.yml/badge.svg)
[![pypi version](https://img.shields.io/pypi/v/volttron-lib-fake-driver.svg)](https://pypi.org/project/volttron-lib-fake-driver/)

The FakeDriver is a way to quickly see data published to the message bus in a format that mimics
what a true Driver would produce. This is an extremely simple implementation of the
[VOLTTRON Driver Framework](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/volttron-platform-driver/docs/source/index.html).
[VOLTTRON Driver Framework](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/volttron-platform-driver/index.html).
This driver does not connect to any actual device and instead produces random and or pre-configured values.

# Requires

* python >= 3.10
* volttron >= 10.0
* volttron-lib-base-driver
* volttron-core >= 2.0.0rc0
* volttron-lib-base-driver >= 2.0.0rc0


# Documentation
More detailed documentation can be found on [ReadTheDocs](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/volttron-platform-driver/docs/source/index.html).
The RST source of the documentation for this component is located in the "docs" directory of this repository.
More detailed documentation can be found on [ReadTheDocs](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/volttron-lib-fake-driver_docs_root/docs/source/index.html#fake-driver). The RST source
of the documentation for this component is located in the "docs" directory of this repository.


# Installation
Expand All @@ -31,25 +30,18 @@ Information on how to install of the VOLTTRON platform can be found
1. If it is not already, install the VOLTTRON Platform Driver Agent:

```shell
vctl install volttron-platform-driver --vip-identity platform.driver --start
vctl install volttron-platform-driver --vip-identity platform.driver
```

2. Install the volttron fake driver library:

```shell
pip install volttron-lib-fake-driver
poetry add --directory $VOLTTRON_HOME volttron-lib-fake-driver
```

3. Store device and registry files for the Fake device to the Platform Driver configuration store:
3. Create configurations for a fake device:

* Create a config directory and navigate to it:

```shell
mkdir config
cd config
```

* Navigate to the config directory and create a file called `fake.config` and add the following JSON to it:
* Create a file called `fake.config` and add the following JSON to it:

```json
{
Expand Down Expand Up @@ -104,7 +96,7 @@ Information on how to install of the VOLTTRON platform can be found

4. Observe Data

To see data being published to the bus, install a [Listener Agent](https://pypi.org/project/volttron-listener/):
To see data being published to the bus, install a [Listener Agent](https://github.com/eclipse-volttron/volttron-listener):

```
vctl install volttron-listener --start
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ profile = "black"

[tool.poetry]
name = "volttron-lib-fake-driver"
version = "0.2.0-rc"
version = "2.0.0rc0"
description = "A minimal implementation of a driver for the VOLTTRON platform."
authors = ["VOLTTRON Team <[email protected]>"]
license = "Apache License 2.0"
Expand All @@ -27,10 +27,9 @@ classifiers = [

[tool.poetry.dependencies]
python = ">=3.10,<4.0"
volttron-lib-base-driver = "^0.2.1rc2"
volttron-lib-base-driver = ">=2.0.0rc0"

[tool.poetry.group.dev.dependencies]
volttron-testing = "^0.4.0rc0"
pytest = "^6.2.5"
pytest-cov = "^3.0.0"
mock = "^4.0.3"
Expand Down
113 changes: 60 additions & 53 deletions src/volttron/driver/interfaces/fake/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
import logging
import math
import random

from collections.abc import KeysView
from math import pi
from pydantic import Field

from volttron.driver.base.interfaces import (BaseInterface, BaseRegister, BasicRevert)
from volttron.driver.base.config import PointConfig, RemoteConfig

_log = logging.getLogger(__name__)
type_mapping = {
Expand All @@ -40,12 +44,21 @@
"boolean": bool
}

class FakeRemoteConfig(RemoteConfig):
remote_id: str | None = None


class FakePointConfig(PointConfig):
# TODO: string starting_value.
starting_value: int | float | bool | str = Field(default='sin', alias='Starting Value')
type: str = Field(default='string', alias='Type')


class FakeRegister(BaseRegister):

def __init__(self, read_only, pointName, units, reg_type, default_value=None, description=''):
def __init__(self, read_only, point_name, units, reg_type, default_value=None, description=''):
# register_type, read_only, pointName, units, description = ''):
super(FakeRegister, self).__init__("byte", read_only, pointName, units, description='')
super(FakeRegister, self).__init__("byte", read_only, point_name, units, description='')
self.reg_type = reg_type

if default_value is None:
Expand All @@ -59,8 +72,8 @@ def __init__(self, read_only, pointName, units, reg_type, default_value=None, de

class EKGregister(BaseRegister):

def __init__(self, read_only, pointName, units, reg_type, default_value=None, description=''):
super(EKGregister, self).__init__("byte", read_only, pointName, units, description='')
def __init__(self, read_only, point_name, units, reg_type, default_value=None, description=''):
super(EKGregister, self).__init__("byte", read_only, point_name, units, description='')
self._value = 1

math_functions = ('acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'sin',
Expand Down Expand Up @@ -88,63 +101,57 @@ def value(self, x):

class Fake(BasicRevert, BaseInterface):

def __init__(self, **kwargs):
super(Fake, self).__init__(**kwargs)

def configure(self, config_dict, registry_config_str):
self.parse_config(registry_config_str)
REGISTER_CONFIG_CLASS = FakePointConfig
INTERFACE_CONFIG_CLASS = FakeRemoteConfig

def get_point(self, point_name):
register = self.get_register_by_name(point_name)
def __init__(self, config: FakeRemoteConfig, *args, **kwargs):
BasicRevert.__init__(self, **kwargs)
BaseInterface.__init__(self, config, *args, **kwargs)

def get_point(self, point_name, **kwargs):
register: FakeRegister = self.get_register_by_name(point_name)
return register.value

def _get_multiple_points(self, topics: KeysView[str], **kwargs) -> (dict, dict):
return BaseInterface.get_multiple_points(self, topics, **kwargs)

def _set_point(self, point_name, value):
register = self.get_register_by_name(point_name)
register: FakeRegister = self.get_register_by_name(point_name)
if register.read_only:
raise RuntimeError("Trying to write to a point configured read only: " + point_name)

register.value = register.reg_type(value)
return register.value

def _scrape_all(self):
result = {}
read_registers = self.get_registers_by_type("byte", True)
write_registers = self.get_registers_by_type("byte", False)
for register in read_registers + write_registers:
result[register.point_name] = register.value

return result

def parse_config(self, configDict):
if configDict is None:
return

for regDef in configDict:
# Skip lines that have no address yet.
if not regDef['Point Name']:
continue

read_only = regDef['Writable'].lower() != 'true'
point_name = regDef['Volttron Point Name']
description = regDef.get('Notes', '')
units = regDef['Units']
default_value = regDef.get("Starting Value", 'sin').strip()
if not default_value:
default_value = None
type_name = regDef.get("Type", 'string')
reg_type = type_mapping.get(type_name, str)

register_type = FakeRegister if not point_name.startswith('EKG') else EKGregister

register = register_type(read_only,
point_name,
units,
reg_type,
default_value=default_value,
description=description)

if default_value is not None:
self.set_default(point_name, register.value)

self.insert_register(register)
def create_register(self, register_definition: FakePointConfig) -> FakeRegister:
read_only = register_definition.writable is not True
description = register_definition.notes
units = register_definition.units
default_value = register_definition.starting_value.strip()
if not default_value:
default_value = None
reg_type = type_mapping.get(register_definition.type, str)

register_type = FakeRegister if not register_definition.volttron_point_name.startswith(
'EKG') else EKGregister

register = register_type(read_only,
register_definition.volttron_point_name,
units,
reg_type,
default_value=default_value,
description=description)

if default_value is not None:
self.set_default(register_definition.volttron_point_name, register.value)
return register

@classmethod
def unique_remote_id(cls, config_name: str, config: RemoteConfig) -> tuple:
"""Unique Remote ID
Subclasses should use this class method to return a hashable identifier which uniquely identifies a single
remote -- e.g., if multiple remotes may exist at a single IP address, but on different ports,
the unique ID might be the tuple: (ip_address, port).
The base class returns the name of the device configuration file, requiring a separate DriverAgent for each.
"""
return (config_name,) if config.remote_id is None else (config.remote_id,)
Loading