Created
November 6, 2018 13:35
-
-
Save nowox/7ce93ec0ef68d9a9e7e3b948e68d271c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
import logging | |
import os | |
import socket | |
import subprocess | |
import sys | |
import json | |
import xml.etree.ElementTree as ET | |
log = logging.getLogger(__name__) | |
ch = logging.StreamHandler(sys.stdout) | |
ch.setLevel(logging.DEBUG) | |
formatter = logging.Formatter('%(asctime)s %(levelname)s - %(message)s') | |
ch.setFormatter(formatter) | |
log.addHandler(ch) | |
log.setLevel(logging.DEBUG) | |
class EnergyPlusAgent(): | |
def __init__(self, **kwargs): | |
self.version = 8.8 | |
self.bcvtb_home = '.' | |
self.model = 'model.idf' | |
self.weather = 'weather.epw' | |
self.socketFile = 'socket.cfg' | |
self.variableFile = 'variables.cfg' | |
self.time = 0 | |
self.vers = 88 | |
self.flag = 0 | |
self.cwd = '/data/fmu' | |
self.sent = None | |
self.rcvd = None | |
self.socketServer = None | |
self.simulation = None | |
self.step = None | |
self.readVariableFile(self.variableFile) | |
def setup(self, sender, **kwargs): | |
log.debug('setup') | |
def start(self, sender, **kwargs): | |
self.startSocketServer() | |
self.startSimulation() | |
def startSocketServer(self): | |
self.socketServer = self.SocketServer() | |
self.socketServer.onRecv = self.recvEnergyPlusMssg | |
self.socketServer.connect() | |
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')) | |
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: | |
self.sent = '%r %r %r 0 0 %r 28.0 1\n' % (self.vers, self.flag, self.ePlusInputs, self.time) | |
log.info("Sending message to EnergyPlus: %s" % repr(self.sent)) | |
self.socketServer.send(self.sent) | |
def recvEnergyPlusMssg(self, mssg): | |
self.rcvd = mssg | |
self.parseEnergyPlusMssg(mssg) | |
def parseEnergyPlusMssg(self, mssg): | |
mssg = str(mssg.rstrip()) | |
log.info('Received message from EnergyPlus: ' + mssg) | |
arry = mssg.split() | |
slot = 6 | |
flag = arry[1] | |
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) | |
self.stop() | |
elif ((int(arry[2]) < self.ePlusOutputs) and (len(arry) < self.ePlusOutputs + 6)): | |
self.exit('Got message with ' + arry[2] + ' inputs. Expecting ' + str(self.ePlusOutputs) + '.') | |
else: | |
data = {'time': 0, 'variables': []} | |
if float(arry[5]): | |
self.time = float(arry[5]) | |
data['time'] = self.time | |
for key in self.outputs: | |
data['variables'].append({key['name']: float(arry[slot].replace("'",''))}) | |
slot += 1 | |
print(json.dumps(data, indent=2, sort_keys=True)) | |
self.sendEnergyPlusMssg() | |
def exit(self, mssg): | |
self.stop() | |
log.error(mssg) | |
def stop(self): | |
if self.socketServer: | |
self.socketServer.stop() | |
def writePortFile(self, path): | |
text = ( | |
'<?xml version="1.0" encoding="ISO-8859-1"?>\n' | |
'<BCVTB-client>\n' | |
' <ipc>\n' | |
' <socket port="%r" hostname="%s"/>\n' | |
' </ipc>\n' | |
'</BCVTB-client>') % (self.socketServer.port, self.socketServer.host) | |
with open(path, "w") as fp: | |
fp.write(text) | |
log.debug("Writing socket file '%s'" % text) | |
log.info("Socket files '%s' created" % path) | |
def readVariableFile(self, path): | |
self.outputs = [] | |
self.inputs = [] | |
for variable in ET.parse(path).getroot().findall('variable'): | |
ep = variable.findall('EnergyPlus')[0] | |
if variable.get('source') == 'Ptolemy': | |
self.inputs.append(ep.get('schedule')) | |
elif variable.get('source') == 'EnergyPlus': | |
self.outputs.append({'name': ep.get('name'), 'type': ep.get('type')}) | |
else: | |
raise ValueError('Invalid source name "%s"' % variable.get('source')) | |
self.ePlusInputs = len(self.inputs) | |
self.ePlusOutputs = len(self.outputs) | |
def onUpdateTopicRpc(self, requester_id, topic, value): | |
self.updateComplete() | |
def onUpdateComplete(self): | |
self.sendEnergyPlusMssg() | |
class SocketServer(): | |
def __init__(self, **kwargs): | |
self.sock = None | |
self.size = 4096 | |
self.client = None | |
self.sent = None | |
self.rcvd = None | |
self.host = 'localhost' | |
self.port = 50051 | |
def onRecv(self, mssg): | |
log.debug('Received %s' % mssg) | |
def run(self): | |
self.listen() | |
def connect(self): | |
if self.host is None: | |
self.host = socket.gethostname() | |
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
if self.port is None: | |
self.sock.bind((self.host, 0)) | |
self.port = self.sock.getsockname()[1] | |
else: | |
self.sock.bind((self.host, self.port)) | |
log.debug('Bound to %r on %r' % (self.port, self.host)) | |
def send(self, mssg): | |
self.sent = mssg | |
if self.client is not None and self.sock is not None: | |
self.client.send(bytes(self.sent, 'utf-8')) | |
def recv(self): | |
if self.client is not None and self.sock is not None: | |
try: | |
mssg = self.client.recv(self.size) | |
except Exception: | |
log.error('We got an error trying to read a message') | |
return mssg | |
def start(self): | |
log.debug('Starting socket server') | |
self.run() | |
def stop(self): | |
if self.sock != None: | |
self.sock.close() | |
def listen(self): | |
self.sock.listen(10) | |
log.debug('Server now listening') | |
self.client, addr = self.sock.accept() | |
log.debug('Connected with ' + addr[0] + ':' + str(addr[1])) | |
log.info('Waiting for simulation start states') | |
mssg = self.recv() | |
if mssg: | |
self.rcvd = mssg | |
self.onRecv(mssg) | |
log.info('Simulation loop') | |
while self.sock: | |
mssg = self.recv() | |
if mssg: | |
self.rcvd = mssg | |
self.onRecv(mssg) | |
def bcvtb_server(): | |
e = EnergyPlusAgent() | |
e.startSocketServer() | |
e.startSimulation() | |
e.socketServer.listen() | |
if __name__ == "__main__": | |
bcvtb_server() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great job man!
Your code is a light in the darkness for me :)) thanks.
I have one question and one request!