def __init__(self, minLatency):
     # minLatency(datetime) ->
     self.minLatency = minLatency
     self.bdm = BDWrapper()
     #		depMap = pickle.load(open(self.depMapFile, 'rb'))
     with open(self.depMapFile, 'rb') as fp:
         depMap = json.load(fp)
     if self.uuid in depMap.keys():
         for depUuid in depMap[self.uuid]:
             self.affectingDependencyDict[depUuid] = timedelta(minutes=10)
             self.affectedDependencyDict[depUuid] = timedelta(minutes=10)
class CommonSetpoint(Actuator):
	bdm = BDWrapper()
	zone = None
	template = None

#TODO: Is it okay to set minVal and maxVal arbitrarily outside this class?
	def __init__ (self, name, uuid, minVal, maxVal, zone):
		self.name = name
		self.uuid = uuid
		super(CommonSetpoint, self).__init__(timedelta(minutes=10))
		self.inputType = master_actuator.TempType(minVal, maxVal)
		self.zone = zone
		self.template = actuNames.commonSetpoint
		self.sensorType = 'PresentValue'

	def set_value(self, val, tp):
		if val=='ZT':
			ztuuid = self.bdm.get_sensor_uuids({'room':self.zone, 'template':sensorNames.zoneTemperature})[0]
			now = datetime.now()
			currZT = self.bdm.get_sensor_ts(ztuuid, 'PresentValue', now-timedelta(hours=1), now).tail().tolist()[0]
			val = currZT
		else:
			pass
		super(CommonSetpoint, self).set_value(val, tp+timedelta(minutes=1))
	
	def get_value(self, beginTime, endTime):
		return super(CommonSetpoint, self).get_value(beginTime, endTime)

	def reset_value(self, val, tp):
		super(CommonSetpoint, self).reset_value(val, tp)
	
	#TODO: Do I need this?
	def set_reset_value(self, resetVal=-1):
		self.resetVal = resetVal
Exemple #3
0
	def __init__(self):
		self.actuNames = ActuatorNames()
		self.sensorNames = SensorNames()
		self.bdm = BDWrapper()
		self.expLogColl = CollectionWrapper('experience_log')
		#self.zonelist = self.csv2list('metadata/partialzonelist.csv')
		self.zonelist = self.csv2list('metadata/zonelist.csv')
		self.feater = FeatureExtractor()
		self.clust = Clusterer()
	def __init__(self, minLatency):
# minLatency(datetime) ->
		self.minLatency = minLatency
		self.bdm = BDWrapper()
#		depMap = pickle.load(open(self.depMapFile, 'rb'))
		with open(self.depMapFile,'rb') as fp:
			depMap = json.load(fp)
		if self.uuid in depMap.keys():
			for depUuid in depMap[self.uuid]:
				self.affectingDependencyDict[depUuid] = timedelta(minutes=10)
				self.affectedDependencyDict[depUuid] = timedelta(minutes=10)
	def __init__(self):
		self.ztTrackZoneList = list()
		self.ntpClient = ntplib.NTPClient()
		self.statColl = CollectionWrapper('status')
		self.expLogColl = CollectionWrapper('experiment_log')
		self.ntpActivateTime = self.dummyBeginTime
		logging.basicConfig(filename='log/debug'+datetime.now().isoformat()[0:-7].replace(':','_') + '.log',level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s')
		#logger = logging.getLogger()
		logging.debug('Quiver initialization')
		self.bdm = BDWrapper()
		self.update_time_offset()
		self.zonelist = basic.csv2list('metadata/zonelist.csv')
		self.depMapFile = 'metadata/dependency_map.json'
	#	requests.packages.urllib3.disable_warnings()

		# Create pid file for monitoring
		pid = str(os.getpid())
class Quiver:
	ntpURL = 'ntp.ucsd.edu'
	timeOffset = timedelta(0)
	ntpClient = None
	actuDict= dict()
	actuNames = ActuatorNames()
	futureCommColl = None 	# This is a collection for future command sequence. If some of the commands are issued, they are removed from here.
	expLogColl = None	 		# This is a collection for log of control. If a command is issued, it is added to here with relevant information.
	statColl = None		# This is a collection for rollback. If a command is issued, its corresponding rollback command is added here.
	relinquishVal = -1
	ambulanceConn = None
	ntpActivateTime = None
	dummyBeginTime = datetime(2000,1,1)
	dummyEndTime = datetime(2030,12,31,0,0,0)
	bdm = None
	ackLatency = timedelta(minutes=10)
	statusExpiration = timedelta(hours=24)
	zonelist = None
	logger = None
	ztTrackZoneList = None

	def __init__(self):
		self.ztTrackZoneList = list()
		self.ntpClient = ntplib.NTPClient()
		self.statColl = CollectionWrapper('status')
		self.expLogColl = CollectionWrapper('experiment_log')
		self.ntpActivateTime = self.dummyBeginTime
		logging.basicConfig(filename='log/debug'+datetime.now().isoformat()[0:-7].replace(':','_') + '.log',level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s')
		#logger = logging.getLogger()
		logging.debug('Quiver initialization')
		self.bdm = BDWrapper()
		self.update_time_offset()
		self.zonelist = basic.csv2list('metadata/zonelist.csv')
		self.depMapFile = 'metadata/dependency_map.json'
	#	requests.packages.urllib3.disable_warnings()

		# Create pid file for monitoring
		pid = str(os.getpid())
		#pidfile = "C:\\temp\\quiver.pid"
#		if os.path.isfile(pidfile):
#			print "%s already exists, exiting" %pidfile
#			sys.exit()
#		else:
#			file(pidfile, 'w').write(pid)
#TODO: Above should be enabled in final version

	def __del__(self):
		pass

	# Construct dependency map for all points
	# Run this when you run this for the fisrt time
	# TODO: For now, it finds all the points in the corresponding zone.
	def init_dependency_map(self):
		depZoneMap = defaultdict(list)
		depUuidMap = defaultdict(list)
		for zone in self.zonelist:
			for actuType in self.actuNames.nameList:
				try:
					uuid = self.get_actuator_uuid(zone, actuType)
					depZoneMap[zone].append(uuid)
				except QRError as e:
					print "There is no sensor corresponding to ", zone, actuType
		for zone, depList in depZoneMap.iteritems():
			for uuid in depList:
				for depUuid in depList:
					if uuid != depUuid:
						depUuidMap[uuid].append(depUuid)

		with open(self.depMapFile,'w') as fp:
			json.dump(depUuidMap, fp)

	def notify_systemfault(self):
		content = "Quiver control system bas been down at " + self.now().isoformat()
		self.notify_email(content)

	def notify_email(self, content):
		server = smtplib.SMTP(emailauth.smtpURL)
		msg = MIMEText('"'+content+'"')
		msg['Subject']='Alert: Quiver is down'
		msg['From'] = emailauth.fromaddr
		msg['To'] = ",".join(emailauth.toaddrs)
		server.starttls()
		server.login(emailauth.username, emailauth.password)
		server.sendmail(emailauth.fromaddr, emailauth.toaddrs, msg.as_string())
		server.quit()

# This is for NTP with UCSD BMS.
# TODO: It should be done in OS itself. Any suggestion?
	def update_time_offset(self):
		ntpRequest = self.ntpClient.request(self.ntpURL)
#		ntpRequest.tx_time
		ntpTime = datetime.strptime(time.ctime(ntpRequest.tx_time), "%a %b %d %H:%M:%S %Y")
		self.timeOffset = ntpTime - datetime.now()
		return ntpTime
	
	# Currently, zone and actuator type is the easiest information for applications to use, so I use it to get uuid, but can be generalized.
	def get_actuator_uuid(self, zone=None, actuType=None):
		context = dict()
		if zone != None:
			context['room']=zone
		if actuType != None:
			context['template']=actuType
		uuids = self.bdm.get_sensor_uuids(context)
		if len(uuids)>1:
			raise QRError('Many uuids are found', context)
		elif len(uuids)==0:
			raise QRError('No uuid is found', context)
		else:
			return uuids[0]
	
	def get_actuator_name(self,zone=None,actuType=None):
		context = dict()
		if zone != None:
			context['room']=zone
		if actuType != None:
			context['template']=actuType
		uuids = self.bdm.get_sensor_names(context)
		if len(uuids)>1:
			raise QRError('Many uuids are found', context)
		elif len(uuids)==0:
			raise QRError('No uuid is found', context)
		else:
			return uuids[0]

	def load_reset_seq(self, endTime):
		query = {'reset_time':{'$lte':endTime}}
		futureSeq = self.statColl.pop_dataframe(query)
		return futureSeq

	def actuator_exist(self, uuid):
# zone(string), actuatortype(string) -> existing?(boolean)
		if uuid in self.actuDict.keys():
			return True
		else:
			return False
			
	def rollback_to_original_setting(self):
		resetSeq = self.load_reset_seq(self.dummyEndTime)
		for row in resetSeq.iterrows():
			currTime = self.now()
			zone = row[1]['zone']
			actuType = row[1]['actuator_type']
			resetVal = row[1]['reset_value']
			uuid = row[1]['uuid']
			actuator = self.actuDict[uuid]
			actuator.reset_value(resetVal, currTime)
		self.statColl.remove_all()

	def validate_command_seq_freq(self,seq):
# seq(pd.DataFrame) -> valid?(boolean)
#TODO: This does not consider reset commands. However, it should be considered later
		baseInvalidMsg = "Test sequence is invalid because "
		for row in seq.iterrows():
			zone = row[1]['zone']
			actuType = row[1]['actuator_type']
			uuid = row[1]['uuid']
			actuator  = self.actuDict[uuid]
			minLatency = actuator.minLatency
			setTime = row[1]['set_time']
			inrangeRowsIdx = np.bitwise_and(seq['set_time']<setTime+minLatency, seq['set_time']>setTime-minLatency)
			inrangeRowsIdx = np.bitwise_and(inrangeRowsIdx, seq['uuid']==uuid)
			inrangeRowsIdx[row[0]] = False
			inrangeRows = seq[inrangeRowsIdx.values.tolist()]
			resetRows = self.statColl.load_dataframe({'uuid':uuid})
			loggedRows = self.expLogColl.load_dataframe({'$and':[{'set_time':{'$gte':setTime-actuator.minLatency}},{'uuid':uuid}]})
			inrangeRows = pd.concat([inrangeRows, resetRows, loggedRows])
			for inrangeRow in inrangeRows.iterrows():
				if inrangeRow[1]['zone']==zone and inrangeRow[1]['actuator_type']==actuType:
					print baseInvalidMsg + str(row[1]) + ' is overlapped with ' + str(inrangeRow[1])
					return row[1]
		return pd.DataFrame({})
	
	def validate_command_seq_dependency(self, seq, minExpLatency):
# seq(pd.DataFrame) -> valid?(boolean)
		#baseInvalidMsg = "Test sequence is invalid because "
		for row in seq.iterrows():
			zone = row[1]['zone']
			actuType = row[1]['actuator_type']
			uuid = row[1]['uuid']
			actuator  = self.actuDict[uuid]
			minLatency = actuator.minLatency
			setTime = row[1]['set_time']
			inrangeRowsIdx = np.bitwise_and(seq['set_time']<setTime, seq['set_time']>setTime-minExpLatency)
			inrangeRows = seq[inrangeRowsIdx.values.tolist()]
			resetRows = self.statColl.load_dataframe({})
			loggedRows = self.expLogColl.load_dataframe({'set_time':{'$gte':setTime-minExpLatency}})
			inrangeRows = pd.concat([inrangeRows, resetRows, loggedRows])
			for inrangeRow in inrangeRows.iterrows():
				if inrangeRow[1]['zone']==zone and actuator.get_dependency(inrangeRow[1]['actuator_type'])!=None:
		#			print baseInvalidMsg + str(row[1]) + ' is dependent on ' + str(inrangeRow[1])
					return row[1]
		return pd.DataFrame({})

	def static_validate(self, seq):
		invalidFreqCommand = self.validate_command_seq_freq(seq)
		if not invalidFreqCommand.empty:
			return invalidFreqCommand
		invalidDepCommand = self.validate_command_seq_dependency(seq, timedelta(minutes=5)) #TODO: This minExpLatency should be set to 1 hour later
		if not invalidDepCommand.empty:
			return invalidDepCommand
		return pd.DataFrame({})

	def validate_batch(self, seq):
		# Validate each command
		for seqRow in seq.iterrows():
			zone = seqRow[1]['zone']
			actuType = seqRow[1]['actuator_type']
			setVal = seqRow[1]['set_value']
			uuid = self.get_actuator_uuid(zone, actuType)
			name= self.get_actuator_name(zone, actuType)
			seqRow[1]['uuid'] = uuid
			seqRow[1]['name'] = name
			
			if not uuid in self.actuDict.keys():
				actuator = metaactuators.make_actuator(uuid, name, zone, actuType)
				if actuator == None:
					logging.error("Incorrect actuator type: %s", actuType)
					raise QRError('Incorrect actuator type', actuType)
				self.actuDict[uuid] = actuator
			actuator = self.actuDict[uuid]

			# Validation 1: Check input range
			if not actuator.validate_input(setVal):
				logging.error("Input value is not in correct range: %s", repr(seqRow[1]))
				raise QRError("Input value is not in correct range", seqRow[1])

			# Validation 2: Check if there is a dependent command in given batch
			for otherSeqRow in seq.iterrows():
				if seqRow[0]!=otherSeqRow[0]:
					if actuator.check_dependency(otherSeqRow[1], self.now()):
						logging.error("A command is dependent on a command in the given sequence: %s", repr(seqRow[1]) + repr(otherSeqRow[1]))
						raise QRError("A command is dependent on a command in the given sequence", repr(seqRow[1]) + repr(otherSeqRow[1]))
					elif otherSeqRow[1]['uuid'] == uuid:
						logging.error('A command has same target equipment with another: %s', repr(seqRow[1])+ repr(otherSeqRow[1]))
						raise QRError('A command has same target equipment with another', repr(seqRow[1])+ repr(otherSeqRow[1]))

			# Validation 3: Check if there is a dependent command in current status
			queryDep = {'set_time':{'$gte':self.now()-actuator.minLatency}}
			depCommands = self.statColl.load_dataframe(queryDep)
			for commRow in depCommands.iterrows():
				if actuator.check_dependency(commRow[1], self.now()):
					logging.error("A command is dependent on current status: %s", repr(seqRow[1])+repr(commRow[1]))
					raise QRError("A command is dependent on current status", repr(seqRow[1])+repr(commRow[1]))

			seq.loc[seqRow[0]] = seqRow[1]

	def now(self):
		currTime = datetime.now()
		currTime = currTime + self.timeOffset
		return currTime

# PRIMARY function.
	def issue_seq(self, seq):
		logging.debug('Start issuing: \n%s', repr(seq))
		self.validate_batch(seq)
		logging.debug('Validation is done')
		#TODO: Check if updated seq is returned

		for row in seq.iterrows():
			idx = row[0]
			zone = row[1]['zone']
			setVal = row[1]['set_value']
			actuType = row[1]['actuator_type']
			uuid = row[1]['uuid']
			name = row[1]['name']
			actuator = self.actuDict[uuid]
			now = self.now()
			origVal = actuator.get_latest_value(now)[0]
			seq.loc[idx, 'original_value'] = origVal
			if actuator.check_control_flag():
				query = {'uuid':uuid}
				resetVal = self.statColl.load_dataframe(query).tail(1)
				#TODO: This should become more safe. e.g., what if that is no data in statColl?
				resetVal = float(resetVal['reset_value'])
			else:
				resetVal = origVal
			seq.loc[idx, 'reset_value'] = resetVal
			setTime = self.now()
			seq.loc[idx, 'set_time'] = setTime
			if setVal == -1:
				if actuType in [self.actuNames.commonSetpoint]:
					setVal = float(self.statColl.load_dataframe({'uuid':uuid}).tail(1)['reset_value'][0])
				actuator.reset_value(setVal, setTime)
				logging.debug('Reset a value: %s by %s', actuType, str(setVal))
			else:
				actuator.set_value(setVal, setTime) #TODO: This should not work in test stage
				logging.debug('Set a value: %s by %s', actuType, str(setVal))
		self.ack_issue(seq)
		logging.debug("Logging done")


#TODO: initialIssueFlagList should be checked if is implemented correctly
	def ack_issue(self, seq):
		if len(seq)==0:
			return None
		seq.index = range(0,len(seq)) # Just to make it sure (need to remove?)
		issueFlagList = np.array([False]*len(seq))
		initialIssueFlagList = np.array([True]*len(seq))
		uploadedTimeList = list()
		resendInterval = timedelta(minutes=6)
		maxWaitTime = resendInterval * 2
		
		# Init uploadedTimeList
		for row in seq.iterrows():
			uuid = row[1]['uuid']
			actuator = self.actuDict[uuid]
			latestVal, setTime = actuator.get_latest_value(self.now())
			setVal = row[1]['set_value']
			# TODO: Think about timing here
			# What if a value is reset then temporal current value is restored?
			# Temporarily do not check the case where setVal==-1.
			if setVal=='-1':
				pass
			elif (setVal!=-1 and latestVal != setVal):
				latestVal, setTime = actuator.get_second_latest_value(self.now())
				if setVal!=-1 and latestVal != setVal:
					initialIssueFlagList[row[0]] = False
					logging.error("A command is not issued initially: \n%s", repr(row[1]))
				#raise QRError('Initial upload to BD is failed', row[1])
			uploadedTimeList.append(setTime)
		uploadedTimeList = np.array(uploadedTimeList)

		# Receive ack
		maxWaitDatetime = max(uploadedTimeList)+maxWaitTime
		ackInterval = 30 # secounds
		while maxWaitDatetime>=self.now(): 
			for row in seq.iterrows():
				idx = row[0]
				if initialIssueFlagList[idx]==False:
					continue
				if issueFlagList[idx]==True:
					continue
				uuid = row[1]['uuid']
				setVal = row[1]['set_value']
				origVal = row[1]['original_value']
				actuator = self.actuDict[uuid]

				if setVal == -1:
					ackVal = row[1]['reset_value']
				else:
					ackVal = row[1]['set_value']
				actuType = row[1]['actuator_type']
				actuator = self.actuDict[uuid]
				currT = self.now()
				currVal, newSetTime = actuator.get_latest_value(self.now())
				logging.debug("ack: uploadTime: %s, downloadTime: %s", str(uploadedTimeList[idx]), str(newSetTime))
				if (setVal!=-1 and currVal==ackVal and newSetTime!=uploadedTimeList[idx]) or (setVal==-1 and currVal!=-1):
					issueFlagList[idx] = True
					seq.loc[idx, 'set_time'] = uploadedTimeList[idx]
					logging.debug("Received a ack of a command: \n%s", repr(row[1]))
					if setVal==-1:
						#actuator.reset_value(currVal, uploadedTimeList[idx])
						pass
					continue
				now = self.now()
				if now>=uploadedTimeList[idx]+resendInterval:
					actuator.set_value(origVal, uploadedTimeList[idx])
					setTime = self.now()
					if setVal==-1:
						if actuType in [self.actuNames.commonSetpoint]:
							setVal = row[1]['reset_value']
						actuator.reset_value(setVal,setTime)
					else:
						actuator.set_value(setVal, setTime)
					logging.debug("Resend a command due to timeout: \n%s", repr(row[1]))
					uploadedTimeList[idx] = setTime
			if not (False in issueFlagList):
				break
			time.sleep(ackInterval)

		for row in seq.iterrows():
			if issueFlagList[row[0]] == True:
				self.reflect_an_issue_to_db(row[1])

#		for idx, flag in enumerate(issueFlagList):
#			if not flag:
#				raise QRError('Some commands are unable to be uploaded', seq[idx])
		if False in issueFlagList or False in initialIssueFlagList:
			logging.error('Some commands are unable to be uploaded: %s', repr(seq[np.logical_not(issueFlagList)]+seq[np.logical_not(initialIssueFlagList)]))
			raise QRError('Some commands are unable to be uploaded', seq[np.logical_not(issueFlagList)]+seq[np.logical_not(initialIssueFlagList)])

	def reflect_an_issue_to_db(self, commDict):
		zone = commDict['zone']
		setVal = commDict['set_value']
		actuType = commDict['actuator_type']
		uuid = commDict['uuid']
		name = commDict['name']
		resetVal = commDict['reset_value']
		setTime = commDict['set_time']
		origVal = commDict['original_value']
		actuator = self.actuDict[uuid]

		self.statColl.pop_dataframe({'uuid':uuid})
		statusRow = StatusRow(uuid, name, setTime=setTime, setVal=setVal, resetVal=resetVal, actuType=actuType, underControl=actuator.check_control_flag())
		self.statColl.store_row(statusRow)
		expLogRow = ExpLogRow(uuid, name, setTime=setTime, setVal=setVal, origVal=origVal)
		self.expLogColl.store_row(expLogRow)
			
	def reset_seq(self, seq):
		for row in seq.iterrows():
			#TODO: validate_in_log
			print row
			resetVal = row[1]['reset_value']
			actuType = row[1]['actuator_type']
			uuid = row[1]['uuid']
			name = row[1]['name']
			actuator = self.actuDict[uuid]
			now = self.now()
			origVal = actuator.get_value(now-timedelta(hours=1), now).tail(1)[0]
			actuator.reset_value(resetVal, resetTime)
			expLogRow = ExpLogRow(uuid, name, setTime=None, setVal=None, resetVal=now, origVal=origVal)
			print expLogRow
			self.expLogColl.store_row(expLogRow)

		if not self.issue_ack(seq, False).empty:
			raise QRError('A reset command cannot be set at BACNet', seq)

	def top_ntp(self):
		ntpLatency = timedelta(minutes=30)
		if self.ntpActivateTime<=self.now():
			self.update_time_offset()
			self.ntpActivateTime = self.now() + ntpLatency

	def system_close_common_behavior(self):
		pass

	def system_refresh(self):
		self.expLogColl.remove_all()
		self.statColl.remove_all()
	

	# rollback does not rollback to original state but rollback by "-1"
	def emergent_rollback(self):
		queryAll = {"under_control":True}
		resetQueue = self.statColl.load_dataframe(queryAll)
		resetQueue = resetQueue.sort(columns='set_time', axis='index')
		maxConcurrentResetNum = 10
		resetInterval = 10*60 # 10 minutes
		if len(resetQueue)==0:
			return None
		resetSeq= defaultdict(dict)
		#resetSeq = [[]]

		# Make actuators to reset and get earliest set time dependent on reset_queue
		earliestDepTime = self.dummyEndTime
		for row in resetQueue.iterrows():
			uuid = row[1]['uuid']
			name = row[1]['name']
			actuType = row[1]['actuator_type']
			if not uuid in self.actuDict.keys():
				actuator = metaactuators.make_actuator(uuid,name,actuType=actuType)
				if actuator == None:
					#raise QRError('Incorrect actuator type', actuType)
					print("Incorrect actuator type: ", actuType)
					continue
				self.actuDict[uuid] = actuator
			setTime = row[1]['set_time']
			dependentTime = setTime - actuator.get_longest_dependency()
			if earliestDepTime > dependentTime:
				earliestDepTime = dependentTime

		# Construct Reset Sequence
		# Filter redundant controls and align with dependency
		for row in resetQueue.iterrows():
			uuid = row[1]['uuid']
			inQFlag = False
			for l in resetSeq.values():
				if uuid in l.keys():
					inQFlag = True
			if inQFlag == True:
				continue
			actuator = self.actuDict[uuid]
			lastKey = 0
			now = self.now()
			row[1]['original_value'] = actuator.get_value(now-timedelta(hours=1), now).tail(1)[0]
			insertedFlag = False
			for k,l in resetSeq.iteritems():
				depList = actuator.get_dependent_actu_list()
				if not bool(set(depList) & set(l.keys())) and len(l)<maxConcurrentResetNum:
					resetSeq[k][uuid] = row[1].to_dict()
					insertedFlag = True
				lastKey = k
			lastKey += 1
			if not insertedFlag:
				resetSeq[lastKey][uuid] = row[1].to_dict()

		for key, stats in resetSeq.iteritems():
			oneResetSeq = pd.DataFrame()
			for uuid, stat in stats.iteritems():
				oneResetSeq = pd.concat([oneResetSeq, pd.DataFrame(stat, index=[0])])
			resetSeq[key] = oneResetSeq

#TODO: need to add checking acknowledgement
		# Rollback
		for k, l in resetSeq.iteritems():
			for key, stat in l.iterrows():
		#		stat = row[1]
				uuid = stat['uuid']
				name = stat['name']
				actuType = stat['actuator_type']
				if actuType in [self.actuNames.commonSetpoint]:
					setVal = stat['reset_value']
				else:
					setVal = -1
				
				actuator = self.actuDict[uuid]
				now = self.now()
				origVal = stat['original_value']
				setTime = self.now()
				expLogRow = ExpLogRow(uuid, name, setTime=setTime, setVal=setVal, origVal=origVal)
				actuator.reset_value(setVal, setTime)
			self.ack_issue(l)
			time.sleep(resetInterval)

	def get_currest_status(self):
		return self.statColl.load_dataframe({'under_control':True})
	
	def output_exp_log(self):
		self.expLogColl.to_csv()
	
	def add_zone_zt_tacking(self,zone):
		self.ztTrackZoneList.append(zone)

	def remove_zone_zt_tacking(self,zone):
		try:
			self.ztTrackZoneList.remove(zone)
		except QRError as e:
			raise QRError('Failed to remove zone from ztTrackZonelist: ', zone)

def resample_data(rawData, beginTime, endTime, sampleMethod):
    rawData = rawData[beginTime:endTime]
    if not beginTime in rawData.index:
        rawData[beginTime] = rawData.head(1)[0]
        rawData = rawData.sort_index()
    if not endTime in rawData.index:
        rawData[endTime] = rawData.tail(1)[0]
        rawData = rawData.sort_index()
    if sampleMethod == 'nextval':
        procData = rawData.resample('2Min', how='pad')
    return procData


bdm = BDWrapper()
sensors_dict = shelve.open('metadata/bacnet_devices.shelve', 'r')

#buildingName = 'ebu3b'
buildingName = sys.argv[1]

rawdataDir = 'rawdata/' + buildingName + '/'

naeDict = dict()
naeDict['ebu3b'] = ['505', '506']
deviceList = naeDict[buildingName]

beginTime = datetime(2016, 1, 18)
endTime = datetime(2016, 1, 25)

# Init raw set
from bd_wrapper import BDWrapper

def resample_data(rawData, beginTime, endTime, sampleMethod):
	rawData = rawData[beginTime:endTime]
	if not beginTime in rawData.index:
		rawData[beginTime] = rawData.head(1)[0]
		rawData = rawData.sort_index()
	if not endTime in rawData.index:
		rawData[endTime] = rawData.tail(1)[0]
		rawData = rawData.sort_index()
	if sampleMethod== 'nextval':
		procData = rawData.resample('2Min', how='pad')
	return procData


bdm = BDWrapper()
sensors_dict = shelve.open('metadata/bacnet_devices.shelve','r')

#buildingName = 'ebu3b'
buildingName = sys.argv[1]


rawdataDir = 'rawdata/'+buildingName+'/'

naeDict = dict()
naeDict['ebu3b'] = ['505', '506']
deviceList = naeDict[buildingName]

beginTime = datetime(2016,1,18)
endTime = datetime(2016,1,25)
Exemple #9
0
class Analyzer:
	bdm = None
	expLogColl = None
	#timeGran = timedelta(minutes=5)
	timeGran = timedelta(minutes=2)
	actuNames = None
	sensorNames = None
	zonelist = None
	feater = None
	clust = None
	
	def __init__(self):
		self.actuNames = ActuatorNames()
		self.sensorNames = SensorNames()
		self.bdm = BDWrapper()
		self.expLogColl = CollectionWrapper('experience_log')
		#self.zonelist = self.csv2list('metadata/partialzonelist.csv')
		self.zonelist = self.csv2list('metadata/zonelist.csv')
		self.feater = FeatureExtractor()
		self.clust = Clusterer()
	
	def csv2list(self, filename):
		outputList = list()
		with open(filename, 'r') as fp:
			reader = csv.reader(fp, delimiter=',')
			for row in reader:
				outputList.append(row[0])
		return outputList

	def get_actuator_uuid(self, zone=None, actuType=None):
		context = dict()
		if zone != None:
			context['room']=zone
		if actuType != None:
			context['template']=actuType
		uuids = self.bdm.get_sensor_uuids(context)
		if len(uuids)>1:
			raise QRError('Many uuids are found', context)
		elif len(uuids)==0:
			raise QRError('No uuid is found', context)
		else:
			return uuids[0]

	def normalize_data_avg(self, rawData, beginTime, endTime):
		procData = pd.Series({beginTime:float(rawData[0])})
		tp = beginTime
		while tp<=endTime:
			tp = tp+self.timeGran
			leftSeries = rawData[:tp]
			if len(leftSeries)>0:
				idx = len(leftSeries)-1
				leftVal = leftSeries[idx]
				leftIdx = leftSeries.index[idx]
			else:
				leftVal = None
			rightSeries = rawData[tp:]
			if len(rightSeries)>0:
				rightVal = rightSeries[0]
				rightIdx = rightSeries.index[0]
			else:
				rightVal = None
			if rightVal==None and leftVal!=None:
				newVal = leftVal
			elif rightVal!=None and leftVal==None:
				newVal = rightVal
			elif tp==leftIdx:
				newVal = leftVal
			elif tp==rightIdx:
				newVal = rightVal
			elif rightVal!=None and leftVal!=None:
				leftDist = (tp - leftIdx).total_seconds()
				rightDist = (rightIdx - tp).total_seconds()
				newVal = (leftVal*rightDist+rightVal*leftDist)/(rightDist+leftDist)
			else:
				print "ERROR: no data found in raw data"
				newVal = None
			newData = pd.Series({tp:newVal})
			procData = procData.append(newData)
		return procData

	def normalize_data_nextval_deprecated(self, rawData, beginTime, endTime):
		procData = pd.Series({beginTime:float(rawData[0])})
		tp = beginTime
		while tp<=endTime:
			tp = tp+self.timeGran
			leftSeries = rawData[:tp]
			if len(leftSeries)>0:
				idx = len(leftSeries)-1
				leftVal = leftSeries[idx]
				leftIdx = leftSeries.index[idx]
			else:
				leftVal = None
			rightSeries = rawData[tp:]
			if len(rightSeries)>0:
				rightVal = rightSeries[0]
				rightIdx = rightSeries.index[0]
			else:
				rightVal = None

			if rightVal != None:
				newVal = rightVal
			else:
				newVal = leftVal

			newData = pd.Series({tp:newVal})
			procData = procData.append(newData)
		return procData

	def normalize_data(self, rawData, beginTime, endTime, normType):
		rawData = rawData[beginTime:endTime]
		if not beginTime in rawData.index:
			rawData[beginTime] = rawData.head(1)[0]
			rawData = rawData.sort_index()
		if not endTime in rawData.index:
			rawData[endTime] = rawData.tail(1)[0]
			rawData = rawData.sort_index()
		if normType=='nextval':
			procData = rawData.resample('2Min', fill_method='pad')
		elif normType=='avg':
			procData = rawData.resample('2Min', how='mean')
		else:
			procData = None

		return procData
		

	def receive_a_sensor(self, zone, actuType, beginTime, endTime, normType):
		print zone, actuType
		uuid = self.get_actuator_uuid(zone, actuType)
		rawData = self.bdm.get_sensor_ts(uuid, 'PresentValue', beginTime, endTime)
		if actuType!=self.actuNames.damperCommand:
			rawData = self.remove_negativeone(rawData)
		procData = self.normalize_data(rawData, beginTime, endTime, normType)
		return procData

	def receive_entire_sensors_notstore(self, beginTime, endTime, normType, exceptZoneList=[]):
		#TODO: Should be parallelized here
		dataDict = dict()
		for zone in self.zonelist:
			if not zone in exceptZoneList:
				dataDict[zone] = self.receive_zone_sensors(zone, beginTime, endTime, normType)
		return dataDict
	
	def receive_entire_sensors(self, beginTime, endTime, filename, normType, exceptZoneList=[]):
#		filename='data/'+beginTime.isoformat()[0:-7].replace(':','_') + '.pkl'
		dataDict = self.receive_entire_sensors_notstore(beginTime, endTime, normType, exceptZoneList=exceptZoneList)
		with open(filename, 'wb') as fp:
			pickle.dump(dataDict, fp)
#			json.dump(dataDict,fp)

	def clustering(self, inputData, dataDict):
		fftFeat = self.feater.get_fft_features(inputData, dataDict)
		minmaxFeat = self.feater.get_minmax_features(dataDict)
		dtwFeat = self.feater.get_dtw_features(inputData, dataDict)
		freqFeat = self.feater.get_freq_features(inputData, dataDict)
		featDict = dict()
		for zone in self.zonelist:
			featList = list()
			featList.append(fftFeat[zone])
			featList.append(minmaxFeat[zone])
			featList.append(dtwFeat[zone])
			#featList.append(freqFeat[zone])
			featDict[zone] = featList
		print featDict['RM-4132']
		return self.clust.cluster_kmeans(featDict)
	
	def remove_negativeone(self, data):
		if -1 in data.values:
			indices = np.where(data==-1)
			for idx in indices:
				data[idx] = data[idx-1]
		return data

	def receive_zone_sensors(self, zone, beginTime, endTime, normType):
		zoneDict = dict()
		for actuType in self.actuNames.nameList+self.sensorNames.nameList:
			if actuType=='Actual Supply Flow':
				pass
			try:
				uuid = self.get_actuator_uuid(zone, actuType)
			except QRError:
				continue
#			if actuType == self.actuNames.commonSetpoint:
#				wcad = self.receive_a_sensor(zone, 'Warm Cool Adjust', beginTime, endTime, normType)
#				data = self.receive_a_sensor(zone, actuType, beginTime, endTime, normType)
#				data = data + wcad
#				pass
			if actuType != self.actuNames.damperCommand:
				if actuType==self.actuNames.occupiedCommand:
					pass
				data = self.receive_a_sensor(zone, actuType, beginTime, endTime, normType)
			else:
				data = self.receive_a_sensor(zone, actuType, beginTime, endTime, normType)
			zoneDict[actuType] = data
		return zoneDict


	def store_zone_sensors(self, zone, beginTime, endTime, normType, filename):
		data = self.receive_zone_sensors(zone, beginTime, endTime, normType)
#		with open(filename, 'wb') as fp:
#			w = csv.DictWriter(fp, data.keys())
#			w.writeheader()
#			w.writerow(data)
		for key, val in data.iteritems():
			val.to_csv('rm4132.csv', header=key, mode='a')

	def store_minmax_dict(self):
		minDict = defaultdict(dict)
		maxDict = defaultdict(dict)
		beginTime = datetime(2015,2,1)
		endTime = datetime(2015,9,1)
		shortBeginTime = datetime(2015,8,1)
		shortEndTime = datetime(2015,8,2)

		for zone in self.zonelist:
			for pointType in self.actuNames.nameList+self.sensorNames.nameList:
				try:
					if pointType=='Occupied Command':
						minDict[zone][pointType] = 1
						maxDict[zone][pointType] = 3
					elif pointType=='Cooling Command':
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = 100
					elif pointType=='Cooling Command' or pointType=='Heating Command':
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = 100
					elif pointType=='Occupied Clg Min' or pointType=='Occupied Htg Flow' or pointType=='Cooling Max Flow':
						uuid = self.get_actuator_uuid(zone, pointType)
						data = self.bdm.get_sensor_ts(uuid, 'Presentvalue', shortBeginTime, shortEndTime)
						minDict[zone][pointType] = min(data)
						maxDict[zone][pointType] = max(data)
					elif pointType=='Temp Occ Sts':
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = 1
					elif pointType=='Reheat Valve Command':
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = 100
					elif pointType=='Actual Supply Flow' or pointType=='Actual Sup Flow SP':
						uuid = self.get_actuator_uuid(zone, pointType)
						data = self.bdm.get_sensor_ts(uuid, 'Presentvalue', shortBeginTime, shortEndTime)
						maxFlow = data[0]
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = maxFlow
					elif pointType=='Damper Position':
						minDict[zone][pointType] = 0
						maxDict[zone][pointType] = 100
					elif pointType=='Damper Command':
						uuid = self.get_actuator_uuid(zone, pointType)
						data = self.bdm.get_sensor_ts(uuid, 'Presentvalue', shortBeginTime, shortEndTime)
						meanData = np.mean(data)
						stdData = np.std(data)
						meanAgain = np.mean(data[np.logical_and(data<=meanData+2*stdData, data>=meanData-2*stdData)])
						minDict[zone][pointType] = meanData-2*stdData
						maxDict[zone][pointType] = meanData+2*stdData
					else:
						uuid = self.get_actuator_uuid(zone, pointType)
						data = self.bdm.get_sensor_ts(uuid, 'Presentvalue', beginTime, endTime)
						minDict[zone][pointType] = min(data)
						maxDict[zone][pointType] = max(data)

				except:
					print "Something is wrong"
					pass
		with open('metadata/mindict.pkl', 'wb') as fp:
			pickle.dump(minDict, fp)
		with open('metadata/maxdict.pkl', 'wb') as fp:
			pickle.dump(maxDict, fp)
class Actuator(object):
    __metaclass__ = ABCMeta
    minLatency = None  # 0 minimum
    inputType = None  # should be selected among above type classes
    #lowerActuators = list()
    #higherActuators = list()
    affectingDependencyDict = dict(
    )  # values of this dict are each actuator's minLatency
    affectedDependencyDict = dict(
    )  # values of this dict are this actuator's minLatency
    controlFlag = False
    uuid = None
    name = None
    bdm = None
    sensorType = None  # ==actuator
    resetVal = None
    depMapFile = 'metadata/dependency_map.json'

    def __init__(self, minLatency):
        # minLatency(datetime) ->
        self.minLatency = minLatency
        self.bdm = BDWrapper()
        #		depMap = pickle.load(open(self.depMapFile, 'rb'))
        with open(self.depMapFile, 'rb') as fp:
            depMap = json.load(fp)
        if self.uuid in depMap.keys():
            for depUuid in depMap[self.uuid]:
                self.affectingDependencyDict[depUuid] = timedelta(minutes=10)
                self.affectedDependencyDict[depUuid] = timedelta(minutes=10)

    @abstractmethod
    def set_value(self, val, tp):
        self.controlFlag = True
        if self.inputType.validate(val):
            self.bdm.set_sensor(self.uuid, self.sensorType, tp, val)
        else:
            print "Failed to validate a value of " + self.zone + '\'s Common Setpoint to ' + str(
                val)

    @abstractmethod
    def get_value(self, beginTime, endTime):
        return self.bdm.get_sensor_ts(self.uuid, self.sensorType, beginTime,
                                      endTime)

    @abstractmethod  #Should this be abm?
    def reset_value(self, val, tp):
        self.bdm.set_sensor(self.uuid, self.sensorType, tp, val)
        self.controlFlag = False

    def get_latest_value(self, now):
        result = self.bdm.get_sensor_ts(self.uuid, self.sensorType,
                                        now - timedelta(hours=6),
                                        now + timedelta(minutes=10))
        if result.empty:
            return None
        else:
            return result.tail(1).values[0], result.tail(1).index[0]

    def get_second_latest_value(self, now):
        result = self.bdm.get_sensor_ts(self.uuid, self.sensorType,
                                        now - timedelta(hours=6),
                                        now + timedelta(minutes=10))
        if len(result) <= 1:
            return None
        else:
            return result.tail(2).values[1], result.tail(2).index[1]

    def check_control_flag(self):
        return self.controlFlag

    def check_dependency(self, commDict, setTime):
        if (commDict['set_time'] >
                setTime - self.minLatency) and commDict['uuid'] == self.uuid:
            return True
        else:
            return False

    def get_dependency(self, uuid):
        if uuid in self.affectingDependencyDict.keys():
            return self.affectingDependencyDict[uuid]
        elif uuid in self.affectedDependencyDict.keys():
            return self.affectedDependencyDict[uuid]
        else:
            return None

    def get_dependent_actu_list(self):
        return self.affectingDependencyDict.keys(
        ) + self.affectedDependencyDict.keys()

    def get_longest_dependency(self):
        return max(max(self.affectedDependencyDict.values()),
                   max(self.affectingDependencyDict.values()))

    def validate_input(self, given):
        return self.inputType.validate(given)
class Actuator(object):
	__metaclass__ = ABCMeta
	minLatency = None # 0 minimum
	inputType = None # should be selected among above type classes
	#lowerActuators = list() 
	#higherActuators = list()
	affectingDependencyDict = dict() # values of this dict are each actuator's minLatency
	affectedDependencyDict = dict() # values of this dict are this actuator's minLatency
	controlFlag = False 
	uuid = None
	name = None
	bdm = None
	sensorType = None # ==actuator
	resetVal = None
	depMapFile = 'metadata/dependency_map.json'

	def __init__(self, minLatency):
# minLatency(datetime) ->
		self.minLatency = minLatency
		self.bdm = BDWrapper()
#		depMap = pickle.load(open(self.depMapFile, 'rb'))
		with open(self.depMapFile,'rb') as fp:
			depMap = json.load(fp)
		if self.uuid in depMap.keys():
			for depUuid in depMap[self.uuid]:
				self.affectingDependencyDict[depUuid] = timedelta(minutes=10)
				self.affectedDependencyDict[depUuid] = timedelta(minutes=10)

	@abstractmethod
	def set_value(self, val, tp):
		self.controlFlag = True
		if self.inputType.validate(val):
			self.bdm.set_sensor(self.uuid, self.sensorType, tp, val)
		else:
			print "Failed to validate a value of " + self.zone + '\'s Common Setpoint to ' + str(val)

	@abstractmethod
	def get_value(self, beginTime, endTime):
		return self.bdm.get_sensor_ts(self.uuid, self.sensorType, beginTime, endTime)

	@abstractmethod #Should this be abm?
	def reset_value(self, val, tp):
		self.bdm.set_sensor(self.uuid, self.sensorType, tp, val)
		self.controlFlag = False

	def get_latest_value(self, now):
		result = self.bdm.get_sensor_ts(self.uuid, self.sensorType, now-timedelta(hours=6), now+timedelta(minutes=10))
		if result.empty:
			return None
		else:
			return result.tail(1).values[0], result.tail(1).index[0]
	
	def get_second_latest_value(self,now):
		result = self.bdm.get_sensor_ts(self.uuid, self.sensorType, now-timedelta(hours=6), now+timedelta(minutes=10))
		if len(result)<=1:
			return None
		else:
			return result.tail(2).values[1], result.tail(2).index[1]
	
	def check_control_flag(self):
		return self.controlFlag

	def check_dependency(self, commDict, setTime):
		if (commDict['set_time']> setTime-self.minLatency) and commDict['uuid']==self.uuid:
			return True
		else:
			return False
		

	def get_dependency(self, uuid):
		if uuid in self.affectingDependencyDict.keys():
			return self.affectingDependencyDict[uuid]
		elif uuid in self.affectedDependencyDict.keys():
			return self.affectedDependencyDict[uuid]
		else:
			return None
	
	def get_dependent_actu_list(self):
		return self.affectingDependencyDict.keys() + self.affectedDependencyDict.keys()
	
	def get_longest_dependency(self):
		return max(max(self.affectedDependencyDict.values()),max(self.affectingDependencyDict.values()))

	def validate_input(self, given):
		return self.inputType.validate(given)