-
Notifications
You must be signed in to change notification settings - Fork 1
/
butler.py
317 lines (247 loc) · 13.4 KB
/
butler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# Purpose: perform a search algorithm to find the most optimal combinations for fixtures optimizing energy efficiency
import queue
from contributionValues import Contribution
import random
class Butler:
def __init__(self):
f = open("sensorvals.txt")
lines = f.readlines()
sensor_values = {}
for line in lines:
k, v = line.strip().split(',')
sensor_values[k] = int(v)
f.close()
f = open("profilevals.txt")
lines = f.readlines()
study_values = {}
movie_values = {}
sleep_values = {}
clean_values = {}
music_values = {}
constribution_values = {}
for line in lines:
act, k, v = line.strip().split(',')
if act == "study":
study_values[k] = int(v)
elif act == "movie":
movie_values[k] = int(v)
elif act == "sleep":
sleep_values[k] = int(v)
elif act == "clean":
clean_values[k] = int(v)
elif act == "music":
music_values[k] = int(v)
f.close()
self.outside_brightness = sensor_values['outside_brightness']
self.outside_temperature = sensor_values['outside_temperature']
self.study_brightness = study_values['light']
self.movie_brightness = movie_values['light']
self.sleep_brightness = sleep_values['light']
self.clean_brightness = clean_values['light']
self.music_brightness = music_values['light']
self.study_temperature = study_values['temp']
self.movie_temperature = movie_values['temp']
self.sleep_temperature = sleep_values['temp']
self.clean_temperature = clean_values['temp']
self.music_temperature = music_values['temp']
# Constants for light energy consumption (adjust as needed)
self.ENERGY_CONSUMPTION = {
'L1': 0.5, # Energy consumption rate for light fixture L1
'L2': 0.7, # Energy consumption rate for light fixture L2
'L3': 0.6, # Energy consumption rate for light fixture L3
'L4': 0.8 # Energy consumption rate for light fixture L4
}
self.MAXBRIGHTNESS = 10
self.MINBRIGHTNESS = 0
self.EFFICIENCY = 0.05
self.lights = ['L1', 'L2', 'L3', 'L4']
# Constants for temperature
self.RESTINC = 0.5
self.HEATINC = 1
self.COOLINC = 1
self.ENERGYCOST = 10
self.MAXTEMP = 50
self.MINTEMP = 0
self.EFFICIENCY = 0.05
# Magic numbers for temperature
self.REST = 0
self.HEAT = 1
self.COOL = 2
# Light Util
def getLightCost(self, lights):
#the cost is based on individual light intensities, and energy consumption rate (closer to 1 -- higher consumption rate, closer to 0 -- lower consumption rate)
cost = self.ENERGY_CONSUMPTION['L1']*lights['L1'] + self.ENERGY_CONSUMPTION['L2']*lights['L2'] + self.ENERGY_CONSUMPTION['L3']*lights['L3'] + self.ENERGY_CONSUMPTION['L4']*lights['L4']
return cost
# this weight is calculated to find the weights of outside and inside brightness
# weights are favoured from 2 factors:
# - outside brightness contribution to total light brightness (calculated in Cotribution class)
# - current outside brightness
def findAdditionalWeights(self, outside, intensity):
# check inside contribution values based off what the current state of outside brightness is and outside contribution
T1 = 10 - (outside*intensity['outsideContribution'])
# remainder of T1
T2 = (10-T1)/10
#scale the values from 0 to 1
T1 = T1/10
return [T1, T2]
def getTotalBrightness(self, lightLevels, shutter_status, outside, intensity):
#check shutter status
if (shutter_status) == True:
status = 0
else:
status = 1
#weights for contribution to outside brightness and inside brightness (separately)
[T1, T2] = self.findAdditionalWeights(outside, intensity)
#weighted sum with the intensities
totalBrightness = T1*(intensity['L1']*lightLevels['L1'] + intensity['L2']*lightLevels['L2'] + intensity['L3']*lightLevels['L3'] + intensity['L4']*lightLevels['L4']) + T2*(outside*status)
print(f"current totalBrightness: {totalBrightness}")
# returns a array
# array[0] = brightness rounded to 1 dp
# array[1] = actual brightness (not rounded)
# using min and max --> avoids values from going out of range
return [round(min(self.MAXBRIGHTNESS, max(self.MINBRIGHTNESS, totalBrightness)), 1), totalBrightness]
def heuristic_function(self, state, target_brightness, outside_brightness):
# Calculate heuristic value actual brightness (decimal value) and target brightness
distance_to_target = abs(target_brightness - state)
return distance_to_target
# Light A*
def AStarLight(self, initialLights, targetBrightness, outsideBrightness, option, shutter_status):
#gets intensity heuristics
contribution = Contribution(option)
intensity = contribution.getValues()
print(f"A* light brightness results:")
#implementation of priority queue
q = queue.PriorityQueue()
# keeps track of min cost at that brightness -- our goal is to make sure that cost is min at that brightness level
minCosts = {brightness: float('inf') for brightness in range(self.MAXBRIGHTNESS*10 + 1)}
brightnessStats = self.getTotalBrightness(initialLights, shutter_status, outsideBrightness, intensity)
nextCost = self.getLightCost(initialLights)
nextH = self.heuristic_function(brightnessStats[1], targetBrightness, outsideBrightness)
totCost = nextCost+nextH
#keep track of successor result if queue becomes empty before exhausting possibilities
successorResult = [initialLights, 0]
closestToGoal = nextH
minCosts[brightnessStats[0]*10] = totCost
q.put((totCost,brightnessStats[0],"", initialLights))
#iterates until queue is empty
while not q.empty():
#get the min element using the priority queue
curr = q.get()
print(f"current successor: {curr}")
# case 1 --> keep track of closest result (for case if queue becomes empty before finding result)
if (targetBrightness - curr[1] < closestToGoal):
successorResult = [curr[3], 0]
closestToGoal = targetBrightness - curr[1]
print(f"current closest result: {successorResult}")
# round brightness to a whole number
successor_brightness = round(curr[1])
# case 2 --> lights reach target brightness
if successor_brightness == targetBrightness:
return [curr[3], 0] #gets the light fixture brightnesses and False shutter status
# case 3 --> lights are minimized, but target brightness cannot be reached
if successor_brightness > targetBrightness and curr[3]['L1'] == 0 and curr[3]['L2'] == 0 and curr[3]['L3'] == 0 and curr[3]['L4'] == 0:
if 2 > successor_brightness - targetBrightness: #slightly off
shutter_value = 2
elif 4 > successor_brightness - targetBrightness: #somewhat off
shutter_value = 4
elif 6 > successor_brightness - targetBrightness: # pretty off
shutter_value = 6
elif 8 > successor_brightness - targetBrightness: # very off
shutter_value = 8
elif 10 >= successor_brightness - targetBrightness: # super off
shutter_value = 10
return [curr[3], shutter_value] #gets the light fixture brightnesses and shutter status at a particular setting
#traverse through all the lights in the smart home
for light, brightness in curr[3].items():
# make a copy of light fixture brightnesses
next_state = dict(curr[3])
# we are going to increase one of the lights by 1 or -1 depending on current status
if successor_brightness < targetBrightness:
if (next_state[light] > 9):
continue
next_state[light] += 1
elif successor_brightness > targetBrightness:
if (next_state[light] < 1):
continue
next_state[light] -= 1
brightnessStats = self.getTotalBrightness(next_state, shutter_status, outsideBrightness, intensity)
#edge case --> brightness is slightly above 10
if (brightnessStats[0] > 10):
brightnessStats[0] = 10
nextCost = self.getLightCost(next_state)
nextH = self.heuristic_function(brightnessStats[1], targetBrightness, outsideBrightness)
totCost = nextCost+nextH #calculate f=g+h -> cost to reach node + additional heuristic cost. also, energy efficiency but it shouldnt have as much of an impact on the next choice as reaching the goal quickly should
#checks if totcost is less than the current min cost that the brightness level
if totCost < minCosts[brightnessStats[0]*10]:
minCosts[brightnessStats[0]*10] = totCost
# check if total brightness value are in range
if round(brightnessStats[0]) >= self.MINBRIGHTNESS and round(brightnessStats[0]) <= self.MAXBRIGHTNESS:
# priority queue:
# value 1: total cost -- this should get min every time
# value 2: current brightness
# value 3: path for queue
# value 4: light values
q.put((totCost, brightnessStats[0], curr[2]+ " --> " +str(light), next_state))
return successorResult
# Temperature Util
def getTemp(self, choice, current, outside):
if choice == self.REST:
return current-self.RESTINC if current>outside else current+self.RESTINC
elif choice == self.HEAT:
return current+self.HEATINC
else:
return current-self.COOLINC
def getCost(self, choice):
return 0 if choice == self.REST else self.ENERGYCOST
# Temperature A*
def AStarTemp(self, goal, outside):
goal = round(goal * (50/30))
outside = round(outside * (50/30))
# edge case --> so we can output a result from 1-50
if (goal == 0):
goal = 1
if (outside == 0):
outside = 1
# initialise
minCosts = [99999 for i in range(self.MAXTEMP*2)] #value at index i will indicate minimum cost (energy+distance from goal) to reach temperature i
q = queue.PriorityQueue()
q.put((0, outside, 0, "")) #total cost, current temp (starts at outside/initial), energy cost so far, path of choices (temp for tracing)
# search
while not q.empty():
curr = q.get() #dequeues lowest cost node, essentially picking best out of current options to expand
#goal temperature is found at lowest cost
if curr[1] == goal:
return curr
for i in range(3):
nextTemp = self.getTemp(i, curr[1], outside)
print(f"nextTemp: {nextTemp}")
nextCost = self.getCost(i)
nextH = abs(goal-nextTemp)
totCost = nextCost*self.EFFICIENCY+nextH #calculate f=g+h -> cost to reach node + additional heuristic cost. also, energy efficiency but it shouldnt have as much of an impact on the next choice as reaching the goal quickly should
if totCost < minCosts[int(nextTemp*2)]: #if we previously had a less effective way to reach this temperature, replace it with this way. if there is already a more effective way to reach this point, don't bother continuing this path
minCosts[int(nextTemp*2)] = totCost
if nextTemp > self.MINTEMP and nextTemp < self.MAXTEMP:
q.put((totCost,nextTemp,nextCost,curr[3]+str(i)))
#note - these variables will change since they will be based by gui
print("Random run -- without sensor percepts")
targetBrightness = random.randint(0, 10)
outsideBrightness = random.randint(0, 10)
initialLights = {'L1': 0, 'L2': 0, 'L3': 0, 'L4': 0} #set this as arbitrary default brightness level per fixture
#random option
optionNumber = random.randint(0,4)
if (optionNumber == 0):
option = 'study'
if (optionNumber == 1):
option = 'movie'
if (optionNumber == 2):
option = 'music'
if (optionNumber == 3):
option = 'sleep'
if (optionNumber == 4):
option = 'clean'
shutter_status = False #assume shutters are open -- these will only be true if it is night time
butler = Butler()
result = butler.AStarLight(initialLights, targetBrightness, outsideBrightness, option, shutter_status)
print(f"final result: {result}")
print("\n")
print("Total cost: {}\tFinal temp: {}\tEnergy cost: {}\tPath: {}".format(*butler.AStarTemp(20,10)))