Commit 0d3e2057 authored by Winstead, Christopher's avatar Winstead, Christopher
Browse files

initial commit

parent 5263ea0f
import logging
import sys
from datetime import datetime
from volttron.platform.agent import utils
from volttron.platform.vip.agent import Core, PubSub
from volttron.platform.messaging import headers as headers_mod
from pnnl.pubsubagent.pubsub.agent import SynchronizingPubSubAgent
import time
import math
import json
utils.setup_logging()
log = logging.getLogger(__name__)
class Tstat():
def __init__(self, devID, current_priority, is_on, switch_ok):
self.devID = devID
self.priority = current_priority
self.is_on = is_on
self.can_switch = switch_ok
class PBCcontrolAgent(SynchronizingPubSubAgent):
def __init__(self, config_path, **kwargs):
super(PBCcontrolAgent, self).__init__(config_path, **kwargs)
zone_cfg = json.load(open('./zones.config','r'))
self.numZones = zonecfg["numZones"]
self.maxZones = zonecfg["maxZones"]
self.buildings=['building'+str(i) for i in range(0,self.numZones)]
self.setpoints=["CoolSched"+str(i) for i in range(0,self.numZones)]
self.t0 = [0 for i in self.buildings]
self.control = [30.0 for i in self.buildings]
self.ZNcount = 0
self.temps = [None for i in range(0,self.numZones)]
self.updated = []
self.all_updates = [i for i in range(0,self.numZones)]
@Core.receiver('onsetup')
def setup(self, sender, **kwargs):
super(PBCcontrolAgent, self).setup(sender, **kwargs)
print('setup')
@PubSub.subscribe('pubsub','temps')
def onUpdateTopic2(self, peer, sender, bus, topic, headers, message):
for idx,i in enumerate(self.buildings):
if topic == 'temps/'+str(i):
newTemp = message[0]['temp']
if idx not in self.updated and newTemp!=self.temps[idx]:
self.temps[idx] = newTemp
self.updated.append(idx)
self.ZNcount += 1
self.t0[idx] += 1
if self.ZNcount == self.numZones:
self.ZNcount = 0
self.updated = []
control2 = self.run_control(self.temps, self.t0, self.control)
for idx in range(0, len(control2)):
if control2[idx] != self.control[idx]:
self.t0[idx] = 0
self.control = control2[:]
for ids in range(0,self.numZones):
self.vip.pubsub.publish('pubsub', 'setp/'+str(self.buildings[ids]),
{'header':'no'}, [self.control[ids], 1])
def calculate_priority(self, temp):
min_prior = 0
max_prior = 10
tempData = temp*(9.0/5.0)+32.0
setP = 80.0
current_priority = 0
temp_per_priority = 1.5/(max_prior-min_prior)
temp_diff = tempData - setP
current_priority = min_prior+math.ceil(temp_diff/temp_per_priority);
if (current_priority > max_prior):
current_priority = max_prior
if (current_priority < min_prior):
current_priority = min_prior
#was causing an issue distinguishing between floats and ints
if(current_priority == max_prior):
current_priority = int(current_priority)
return current_priority
def run_control(self, temps, t0, is_on):
equip = []
ctrl = is_on[:]
for idx, t in enumerate(temps):
priority = self.calculate_priority(t)
tmp_isOn = is_on[idx]
if tmp_isOn == 35.0:
tmp_isOn = 0
elif tmp_isOn == 16.0:
tmp_isOn = 1
sw = t0[idx]
if sw > 1:
sw = 1
else:
sw = 0
equip.append(Tstat(idx+1, priority, tmp_isOn, sw))
tmp = self.run_scheduler(equip, self.maxZones)
for idx in range(len(tmp)):
if tmp[idx] == 'activate':
ctrl[idx] = 16.0
elif tmp[idx] == 'shutdown':
ctrl[idx] = 35.0
return ctrl
# Activate or deactivate equipment according to the control rule
def run_scheduler(self, equip, N):
status = []
for i in range(0,len(equip)):
status.append(None)
skip_list = []
activate_list = []
deactivate_list = []
count = 0
active_count = 0
pending_active = 0
pending_deactive = 0
MIN_PRIORITY = 0
MAX_PRIORITY = 10
switched_device = False
# Scan all of the devices, discarded devices that fail to respond
if not equip:
assert(False)
# Sort the devices by priority
equip.sort(key = lambda tstat: tstat.priority, reverse=True)
for i in range(0,len(equip)):
for j in range(0, len(equip)):
if equip[i].priority == equip[j].priority and i != j:
if equip[i].can_switch > equip[j].can_switch:
equip[i], equip[j] = equip[j],equip[i]
else:
pass
else:
pass
if equip[0].priority < equip[len(equip)-1].priority:
assert(False)
active_count = 0
for tstat in equip:
if tstat.is_on and not tstat.can_switch:
active_count+=1
for tstat in equip:
if tstat.is_on:
if tstat.can_switch:
if tstat.priority != MIN_PRIORITY:
if active_count < N or tstat.priority == MAX_PRIORITY:
activate_list.append(tstat)
active_count+=1
elif active_count >= N and tstat.priority!=MAX_PRIORITY:
deactivate_list.insert(0,tstat)
elif tstat.priority == MIN_PRIORITY:
deactivate_list.insert(0,tstat)
elif not tstat.is_on:
if tstat.can_switch:
if tstat.priority != MIN_PRIORITY:
if active_count < N or tstat.priority == MAX_PRIORITY:
activate_list.append(tstat)
active_count+=1
if deactivate_list and len(deactivate_list) != 1 and \
(deactivate_list[0].priority > deactivate_list[len(deactivate_list)-1].priority):
assert(False)
if activate_list != [] and (len(activate_list) != 1 and \
activate_list[0].priority < activate_list[len(activate_list)-1].priority):
assert(False)
# Shutdown priority zero devices and as many others as are needed to
# enable the new devices to run or get below our limit
for tstat in deactivate_list:
if tstat.priority >= MAX_PRIORITY:
assert(False);
status[tstat.devID-1] = 'shutdown'
switched_device = True
# Active as many devices as we can
for tstat in activate_list:
status[tstat.devID-1] = 'activate'
switched_device = True
my_slot = 0
for tstat in equip:
my_slot =+ 1
if (my_slot > N and tstat.priority < MAX_PRIORITY \
and tstat.is_on == 1 and tstat.can_switch == 1):
slot = 0
for other in equip:
slot =+ 1
assert(other.priority >= tstat.priority)
if (slot <= N):
assert(other != tstat)
assert(other.priority == tstat.priority \
or other.is_on or other.can_switch == 0)
else:
break
return status
def main(argv=sys.argv):
'''Main method called by the eggsecutable.'''
try:
utils.vip_main(PBCcontrolAgent)
except Exception as e:
log.exception(e)
if __name__ == '__main__':
# Entry point for script
sys.exit(main())
{"maxZones": 72, "numZones": 146}
\ No newline at end of file
# -*- coding: utf-8 -*- {{{
# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et:
#
# Copyright (c) 2016, Battelle Memorial Institute
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.
#
# 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,
# r 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 setuptools import setup, find_packages
#get environ for agent name/identifier
packages = find_packages('.')
package = packages[0]
setup(
name = package,
version = "0.1",
install_requires = ['volttron','./zones.config'],
packages = packages,
entry_points = {
'setuptools.installation': [
'eggsecutable = ' + package + '.agent:main',
]
}
)
# -*- coding: utf-8 -*- {{{
# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et:
#
# Copyright (c) 2016, Battelle Memorial Institute
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.
#
# 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,
# r 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 __future__ import absolute_import
import chardet
import logging
import os
import socket
import subprocess
import sys
from datetime import datetime
from gevent import monkey
monkey.patch_socket()
from volttron.platform.agent import utils
from volttron.platform.vip.agent import Core, RPC
from pnnl.pubsubagent.pubsub.agent import SynchronizingPubSubAgent
utils.setup_logging()
log = logging.getLogger(__name__)
SUCCESS = 'SUCCESS'
FAILURE = 'FAILURE'
class EnergyPlusAgent(SynchronizingPubSubAgent):
def __init__(self, config_path, **kwargs):
super(EnergyPlusAgent, self).__init__(config_path, **kwargs)
self.version = 8.8
self.bcvtb_home = '.'
self.model = None
self.weather = None
self.socketFile = None
self.variableFile = None
self.time = 0
self.vers = 2
self.flag = 0
self.sent = None
self.rcvd = None
self.socketServer = None
self.simulation = None
self.step = None
self.ePlusInputs = 0
self.ePlusOutputs = 0
if not self.config:
self.exit('No configuration found.')
self.cwd = os.getcwd()
@Core.receiver('onsetup')
def setup(self, sender, **kwargs):
super(EnergyPlusAgent, self).setup(sender, **kwargs)
@Core.receiver('onstart')
def start(self, sender, **kwargs):
self.subscribe()
self.startSocketServer()
self.startSimulation()
def startSocketServer(self):
self.socketServer = self.SocketServer()
self.socketServer.onRecv = self.recvEnergyPlusMssg
self.socketServer.connect()
self.core.spawn(self.socketServer.start)
def startSimulation(self):
if not self.model:
self.exit('No model specified.')
if not self.weather:
self.exit('No weather specified.')
modelPath = self.model
if (modelPath[0] == '~'):
modelPath = os.path.expanduser(modelPath)
if (modelPath[0] != '/'):
modelPath = os.path.join(self.cwd,modelPath)
weatherPath = self.weather
if (weatherPath[0] == '~'):
weatherPath = os.path.expanduser(weatherPath)
if (weatherPath[0] != '/'):
weatherPath = os.path.join(self.cwd,weatherPath)
modelDir = os.path.dirname(modelPath)
bcvtbDir = self.bcvtb_home
if (bcvtbDir[0] == '~'):
bcvtbDir = os.path.expanduser(bcvtbDir)
if (bcvtbDir[0] != '/'):
bcvtbDir = os.path.join(self.cwd,bcvtbDir)
log.debug('Working in %r', modelDir)
self.writePortFile(os.path.join(modelDir,'socket.cfg'))
self.writeVariableFile(os.path.join(modelDir,'variables.cfg'))
if (self.version >= 8.4):
cmdStr = "cd %s; export BCVTB_HOME=\"%s\"; energyplus -w \"%s\" -r \"%s\"" % (modelDir, bcvtbDir, weatherPath, modelPath)
else:
cmdStr = "export BCVTB_HOME=\"%s\"; runenergyplus \"%s\" \"%s\"" % (bcvtbDir, modelPath, weatherPath)
log.debug('Running: %s', cmdStr)
self.simulation = subprocess.Popen(cmdStr, shell=True)
def sendEnergyPlusMssg(self):
if self.socketServer:
args = self.input()
mssg = '%r %r %r 0 0 %r' % (self.vers, self.flag, self.ePlusInputs, self.time)
for obj in args.itervalues():
if obj.get('name', None) and obj.get('type', None):
mssg = mssg + ' ' + str(obj.get('value'))
self.sent = mssg+'\n'
log.info('Sending message to EnergyPlus: ' + mssg)
self.socketServer.send(self.sent)
def recvEnergyPlusMssg(self, mssg):
self.rcvd = mssg
self.parseEnergyPlusMssg(mssg)
self.publishAllOutputs()
def parseEnergyPlusMssg(self, mssg):
print(mssg)
mssg = mssg.rstrip()
log.info('Received message from EnergyPlus: ' + mssg)
arry = mssg.split()
slot = 6
flag = arry[1]
output = self.output()
if flag != '0':
if flag == '1':
self.exit('Simulation reached end: ' + flag)
elif flag == '-1':
self.exit('Simulation stopped with unspecified error: ' + flag)
elif flag == '-10':
self.exit('Simulation stopped with error during initialization: ' + flag)
elif flag == '-20':
self.exit('Simulation stopped with error during time integration: ' + flag)
else:
self.exit('Simulation stopped with error code ' + flag)
elif ((arry[2] < self.ePlusOutputs) and (len(arry) < self.ePlusOutputs+6)):
self.exit('Got message with ' + arry[2] + ' inputs. Expecting ' + str(self.ePlusOutputs) + '.')
else:
if float(arry[5]):
self.time = float(arry[5])
for key in output:
if self.output(key, 'name') and self.output(key, 'type'):
try:
self.output(key, 'value', float(arry[slot]))
except:
self.exit('Unable to convert received value to double.')
slot += 1
def exit(self, mssg):
self.stop()
log.error(mssg)
def stop(self):
if self.socketServer:
self.socketServer.stop()
self.socketServer = None
def writePortFile(self, path):
fh = open(path, "w+")
fh.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
fh.write('<BCVTB-client>\n')
fh.write(' <ipc>\n')
fh.write(' <socket port="%r" hostname="%s"/>\n' % (self.socketServer.port, self.socketServer.host))
fh.write(' </ipc>\n')
fh.write('</BCVTB-client>')
fh.close()
def writeVariableFile(self, path):
fh = open(path, "w+")
fh.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
fh.write('<!DOCTYPE BCVTB-variables SYSTEM "variables.dtd">\n')
fh.write('<BCVTB-variables>\n')
for obj in self.output().itervalues():
if obj.has_key('name') and obj.has_key('type'):
self.ePlusOutputs = self.ePlusOutputs + 1
fh.write(' <variable source="EnergyPlus">\n')
fh.write(' <EnergyPlus name="%s" type="%s"/>\n' % (obj.get('name'), obj.get('type')))
fh.write(' </variable>\n')
for obj in self.input().itervalues():
if obj.has_key('name') and obj.has_key('type'):
self.ePlusInputs = self.ePlusInputs + 1
fh.write(' <variable source="Ptolemy">\n')
fh.write(' <EnergyPlus %s="%s"/>\n' % (obj.get('type'), obj.get('name')))
fh.write(' </variable>\n')
fh.write('</BCVTB-variables>\n')
fh.close()