Run the following commands:
git clone https://github.com/VOLTTRON/volttron --branch releases/7.x
cd volttron
python bootstrap.py
source env/bin/activate
vcfg
- to run a single instance of Volttron platform select Y to all boolean options presented and default values for all others. This should install Volttron Central module into your environment../start-volttron
You can then verify if your agents are live by running command vctl status
. You should see a list of all agents running in the system.
Then you shall proceed to https://hostname:8443/admin/login.html (hostname is what you've chosen during configuration) and create a user account. You can then login to Volttron Central https://hostname:8443/vc/index.html.
We use Volttron platform as communication medium for multiple agents that cooperate to reduce household energy consumption.
For the sake of simplicity we consider a single home equipped with a set of photovoltaic (PV) panels and a number of energy receivers, such as e.g. washing machines. Each such installation is represented by a single agent in the system.
We introduce a single Hub agent responsible for scheduling working periods of power consumers. This agent receives data from both suppliers and consumers. On that basis it assigns time slots for the devices that minimize the following quantity:
α * energy_to_buy + β * energy_oversupply + θ * average_delay
For large values of α this encourages usage of free energy from the panels.
Agents responsible for communication with PV panels use Volttron pub-sub functionality to publish power supply data, i.e. power and voltage. As of time being, dummy data is used. We assume that PV panels provide us with an estimate supply for the next 6 hours, on which we base our optimisation.
Power consumer agents publish requests for power to the Hub agent. They transfer the following information:
- requested energy profile – how much power [kWh] is required over a period of time
- maximal delay for the device initialization
HubAgent keeps an instance of Hub
object:
lookahead = 6*4
scheduler = BruteForceScheduler(lookahead)
self.hub = Hub(scheduler, self.vip.pubsub)
It subscribes on power supply and consumption topics:
self.vip.pubsub.subscribe(peer='pubsub',
prefix="devices/AGH/D17/Panel/profile",
callback=self.on_source_request)
self.vip.pubsub.subscribe(peer='pubsub',
prefix="devices/AGH/D17/Device/request",
callback=self.on_device_request)
Then it feeds the Hub
object with all incoming messages:
def on_source_request(self, peer, sender, bus, topic, headers, message):
message[0]['profile'] = np.array(message[0]['profile'])
self.hub.update_source_profile(message[0]['device'], message[0]['profile'])
def on_device_request(self, peer, sender, bus, topic, headers, message):
message[0]['profile'] = np.array(message[0]['profile'])
request = Request(message[0]['id'], message[0]['device'],
message[0]['profile'], message[0]['timeout'])
self.hub.add_request(request)
And starts a thread responsible for periodic triggering the Hub
object's computations:
def routine(self):
while True:
try:
self.hub.tick()
self.report_results()
except Exception as e:
print(e)
time.sleep(1)
Power supply data comes from SolarPanel agent. It publishes simulated solar power profiles to the topic, which HubAgent is subscribed to. The weather_factor
ranges between 0.3 and 1.0, depending on weather data obtained with pyowm
library.
while True:
for i in range(24*4):
if i%4 == 0:
self.set_weather_factor()
profile = self.simulate_solar_profile(i)
request = {
'device': 'SolarAgent',
'profile': list(profile),
'timeout': 0,
'id': 0
}
self.vip.pubsub.publish('pubsub', "devices/AGH/D17/Panel/profile", message=[request])
time.sleep(1)
WashingAgent acts as a washing machine. It publishes a profile of energy to be consumed, which, after being received by HubAgent, will be used for scheduling:
request = {
'device': 'WashingMachine1',
'profile': [0.1, 0.2, 0.3, 0.3, 0.2, 0.2, 0.4],
'timeout': 15,
'id': random.getrandbits(128)
}
self.vip.pubsub.publish('pubsub', "devices/AGH/D17/Device/request", message=[request])
Energy consumption of single execution of one device over a period of time is called a profile. Profiles are one-dimensional arrays of floating point numbers, where each sample represents average energy usage in a single time unit (e.g. 15 minutes).
dishwasher_profile = np.array([0.1, 0.2, 0.2, 0.1, 0.3, 0.3, 0.1, 0.3, 0.1])
Profiles can also represent estimated energy production, e.g. of a solar panel:
solar_panel_profile = np.array([0, 0, 0.08, 0.17, 0.23, 0.36, 0.40, 0.41, 0.42, 0.39])
The intention of single execution of a device is called a request. Request consists of an unique identifer, device's name, device's energy profile and maximal execution delay called timeout. Immediate start (with no delay) is represented by zero timeout.
request = Request(request_id=1342, device_name='dishwasher1', profile=dishwasher_profile, timeout=10)
Different planning strategies are encapsulated as schedulers. Schedulers implement schedule
method that accepts a profile of available energy with a list of waiting requests and returns the optimal execution plan. Currently there are three available schedulers:
NoDelayScheduler
returns the most trivial execution plan that runs all pending requests without any delay.
scheduler = NoDelayScheduler()
BruteForceScheduler
finds the best combination of delays by checking all possible delays for all requests. Please note that this scheduler is very resource-hungry.
scheduler = BruteForceScheduler(lookahead=20)
LinearProgrammingScheduler
reduces scheduling problem to Mixed Integer Programming instance and finds the optimal solution using ILP solver.
scheduler = LinearProgrammingScheduler(lookahead=20)
Both BruteForceScheduler
and LinearProgrammingScheduler
need to be parametrized with a number of ticks to plan ahead. Each request will start in request.timeout
ticks and end before scheduler.lookahead
ticks.
Execution plan is represented in a form of dictionary where keys are the requests' identifiers and values are the calculated delays for corresponding requests.
{1342: 2, 1343: 0, 1344: 5}
Energy source's profiles and device's requests are transferred to a single hub. Hub is responsible for accepting requests, preparing the execution plan and sending launch permits to the devices in the appropriate time. Hub must be initialized with an instance of scheduler.
hub = Hub(scheduler)
When an updated profile of energy source is published, update_source_profile
method should be called:
hub.update_source_profile(source_name='solarpanel1', profile=solar_panel_profile)
When a new request is received, add_request
method has to be called.
hub.add_request(request1)
When adding multiple requests one-by-one, you may turn off autoscheduling by adding autoschedule=False
flag.
for request in requests:
hub.add_request(request, autoschedule=False)
...
hub.schedule()
Alternatively, you may use add_requests
method that runs the scheduler only once.
hub.add_requests([request2, request3, request4])
You may also temporarily use different scheduling strategy by passing other scheduler instance, e.g. for comparision purposes.
hub.schedule_with(NoDelayScheduler())
In order to simulate the passage of time, the tick
must be called. Every tick launches jobs that should start in the current tick and decreases other waiting requests' timeouts by 1.
hub.tick()
When using hub object in a VOLTTRON™ agent it is necessary to call tick
method periodically:
from volttron.platform.scheduling import cron
@Core.schedule(cron('*/5 * * * *'))
def cron_function(self):
self.hub.tick()
Without optimization | With optimization |
---|---|
Without optimization | With optimization |
---|---|
Without optimization | With optimization |
---|---|