-
Notifications
You must be signed in to change notification settings - Fork 0
/
TestingManager.py
350 lines (296 loc) · 10.6 KB
/
TestingManager.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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
'''
=============================
Title: Testing and Data Collection - EDS Field Control
Author: Benjamin Considine
Editor: Brian Mahabir, Aditya Wikara
Started: September 2018
=============================
'''
import RPi.GPIO as GPIO
import time
import math
import os
import subprocess
import busio
import digitalio
import board
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
# adc constants
#ADC_PV_CHAN = 1
#ADC_BAT_CHAN = 2
#SPI_PORT = 0
#SPI_DEVICE = 0
VREF = 3.3
STEPS = 1023
# year days for start of each month (because the clock doesn't want to keep tm_yday for some reason)
# don't care about leap year
Y_DAYS = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
# tolerances for humidity and temperature scheduling
T_TOL = 0.1
H_TOL = 0.1
# how close current time must be to scheduled time to initiate test (min)
MIN_CHECK_THRESHOLD = 0.5
# GPIO setup
GPIO.cleanup()
# simplified channel functions
def channel_out(chn, val):
GPIO.output(chn, val)
def channel_in(chn):
return GPIO.input(chn)
'''
ADC Master Class:
Functionality:
1) Initializes connection with ADC chip
2) Reads and processes raw data from ADC
3) Converts raw data into usable information
4) Logs data into .csv file
'''
class ADCMaster:
def __init__(self):
#GPIO pin to trigger the relay, high is OCV, low is SCC
GPIO.setup(25, GPIO.OUT)
#Properties
#self.mcp = MCP.MCP3008(spi, cs)
self.bat_div = 10
def get_ocv_PV(self):
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.CE0)
mcp = MCP.MCP3008(spi, cs)
chan=AnalogIn(mcp, MCP.P0)
time.sleep(1)
raw = chan.voltage
print('PV Raw volt read: ' + str(raw) + '[V]')
# Since we divided voltage by 11, multiply by 11 to get actual Voc
return raw * 11
def get_scc_PV(self):
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.CE0)
mcp = MCP.MCP3008(spi, cs)
chan=AnalogIn(mcp, MCP.P0)
time.sleep(1)
raw = chan.voltage
print('PV Raw curr read: ' + str(raw) + '[A]')
#SCC = Voc x 1 ohm
return raw * 1
def get_ocv_BAT(self):
chan=AnalogIn(self.mcp, MCP.P0)
raw = self.mcp.read_adc(channel)
print('Battery raw volt read: ' + str(raw) + '[V]')
# voltage divider calc
return self.bat_div * VREF * raw / STEPS
'''
Testing Master Class:
Functionality:
1) Verifies electrical components
2) Executes testing sequence
'''
class TestingMaster:
def __init__(self, config_dictionary):
self.okay_to_test = False
self.test_config = config_dictionary
self.adc_m = ADCMaster()
# simple getter for config dictionary
def get_config(self):
return self.test_config
# gets a float type value for dictionary entry (will need this a lot)
def get_param(self, key):
return float(self.test_config[key])
# get int type from dicationary if GPIO pin value needed
def get_pin(self, key):
return int(self.test_config[key])
# check time against schedule
def check_time(self, dt, yday, solar_offset, eds_num):
sched_name = 'SCHEDS'+str(eds_num)
schedule = self.test_config[sched_name]
for pair in schedule:
# if mod of the year day with schedule is zero, then day is correct
if yday % float(pair[0]) == 0:
# convert current time to minutes and add solar time offset
dt_min = dt.tm_hour * 60 + dt.tm_min + dt.tm_sec / 60
dt_min_solar = dt_min + solar_offset
# check if current minute is close enough to schedule time offset
# calculate schedule time in minutes
schedule_min = 720 + float(pair[1]) * 60
# if the time is within 30 seconds of scheduled time
if abs(dt_min_solar - schedule_min) < MIN_CHECK_THRESHOLD:
return True
# check weather against parameters
def check_temp(self, t_curr):
t_low = self.get_param('minTemperatureCelsius')
t_high = self.get_param('maxTemperatureCelsius')
# check temperature against needed conditions
if ((1 - T_TOL) * t_low) <= t_curr <= (t_high * (1 + T_TOL)): # tolerance allows a bit outside range
return True
else:
print("Temperature (", t_curr, " C) not within testing parameters. Will check again shortly.")
return False
def check_humid(self, h_curr):
h_low = self.get_param('minRelativeHumidity')
h_high = self.get_param('maxRelativeHumidity')
# check humidity against needed conditions
if ((1 - H_TOL) * h_low) <= h_curr <= (h_high * (1 + H_TOL)): # tolerance allows a bit outside range
return True
else:
print("Humidity (", h_curr, " %) not within testing parameters. Will check again shortly.")
return False
# Activating EDS to repel soiling/dust/etc
def run_test(self, eds_num):
# main test sequence to be run after checking flags in MasterManager
test_duration = self.get_param('testDurationSeconds')
# run first half of test
self.run_test_begin(eds_num)
# wait for test duration
time.sleep(test_duration)
# run second half of test
self.run_test_end(eds_num)
# Measure Voc and Isc of EDS
def run_measure_EDS(self, eds_num):
# Get pin for PV relay
pv_relay = self.get_pin('EDS' + str(eds_num) + 'PV')
# Setup GPIO pins to measure Voc and Isc of desired panel
time.sleep(0.5)
GPIO.setup(pv_relay, GPIO.OUT)
GPIO.setup(25, GPIO.OUT)
time.sleep(0.5)
# OCV READ
# Switch the relay to read Voc
GPIO.output(25, GPIO.HIGH)
time.sleep(0.5)
# Get reading
read_ocv = self.adc_m.get_ocv_PV()
# Switch relay back
time.sleep(5)
# SCC READ
# Switch relay to read Isc
GPIO.output(25, GPIO.LOW)
time.sleep(2)
# get reading
read_scc = self.adc_m.get_scc_PV()
# Default pin is LOW, no need to switch, just clean up
time.sleep(2)
GPIO.cleanup(25)
# Close EDS PV Relay
time.sleep(2)
GPIO.cleanup(pv_relay)
time.sleep(2)
return [read_ocv, read_scc]
def run_measure_CTRL(self, ctrl_num):
# Get pin for PV relay
pv_relay = self.get_pin('CTRL' + str(ctrl_num) + 'PV')
# Setup GPIO pins to measure Voc and Isc of desired panel
time.sleep(0.5)
GPIO.setup(pv_relay, GPIO.OUT)
time.sleep(0.5)
GPIO.setup(25, GPIO.OUT)
time.sleep(0.5)
# OCV READ
# Switch the relay to read Voc
GPIO.output(25, GPIO.HIGH)
time.sleep(1)
# Get reading
read_ocv = self.adc_m.get_ocv_PV()
# Switch relay back
time.sleep(1)
# SCC READ
# Switch relay to read Isc
GPIO.output(25, GPIO.LOW)
time.sleep(1)
# get reading
read_scc = self.adc_m.get_scc_PV()
# Default pin is LOW, no need to switch, just clean up
time.sleep(1)
GPIO.cleanup(25)
# Close EDS PV Relay
time.sleep(0.5)
GPIO.cleanup(pv_relay)
time.sleep(0.5)
return [read_ocv, read_scc]
def run_measure_BAT(self):
# the battery will not require flipping relays/transistors (only ~14uW power lost)
# get reading
read_ocv = self.adc_m.get_ocv_BAT()
return read_ocv
def run_test_begin(self, eds_num):
# runs the first half of a test (pauses on test duration to allow for indefinite testing)
eds_select = self.get_pin('EDS'+str(eds_num))
# EDS activation relays ON
GPIO.setup(eds_select, GPIO.OUT)
GPIO.output(eds_select, 1)
# short delay between relay switching
time.sleep(0.5)
def run_test_end(self, eds_num):
# runs the second half of a test to finish from first half
eds_select = self.get_pin('EDS'+str(eds_num))
#ps_relay = self.get_pin('POWER')
# THIS MUST FOLLOW run_test_begin() TO FINISH TEST PROPERLY
# deactivate the EDS
GPIO.output(eds_select, 0)
GPIO.cleanup(eds_select)
time.sleep(0.5)
'''
Power Master Class:
Functionality:
1) Takes in voc, isc, and temperature as inputs
2) Measures power output
'''
class PowerMaster:
def __init__(self):
#Solar Panel Specifications
self.v_mp = 17 #Operating voltage/max point voltage
self.i_mp = 0.58 #Operating current/max point current
self.v_nom = 12 #Nominal voltage
self.p_max = 10 #Max Power
self.voc = 21.5 #Open Circuit Voltage
self.isc = 0.68 #Short Circuit Current
#Readings from sensors defaults to -1
self.v_oc = -1
self.i_sc = -1
self.temp = -1
def get_power_out(self,v_oc,i_sc,temp):
#Get normalized open circuit voltage
v_norm = self.voltage_normalized(v_oc, temp)
#Compute the fill factor
FF = self.fill_factor(v_norm)
#Calculate the output power
p_out = v_oc * i_sc * FF
return p_out
def fill_factor(self,v_norm):
#Compute fill factor using normalized voltage
FF = (v_norm - math.log(v_norm+0.72))/(v_norm+1)
return FF
def voltage_normalized(self,v_oc,temp):
#Coulomb constant
q = 1.6*10**-19
#Ideality factor, 1 for Si
n = 1
#Boltzmann Constant
k = 1.38*10**-23
#Calculate normalized temperature
cells = 36 #number of cells in the solar panel
v_norm = (v_oc/cells) * (q/(n*k*temp))
return v_norm
def get_panel_temp(self,amb_temp, g_poa):
noct = 47 #This needs to be confirmed
t_pan = amb_temp + ((noct - 20)*g_poa)/800
return t_pan
'''
Performance Ratio Class:
Functionality:
1) Takes in voc, isc, and temperature as inputs
2) Measures power output
'''
class PerformanceRatio:
def __init__(self):
#Summation of installed module's namemplate rating, for field testing just 1 panel so 12W
self.ptc = 12
#Irradiance at STC
self.gstc = 1000
def get_pr(self,v_oc,i_sc,temp,power,gpoa):
if (gpoa == 0):
PR = -1
elif (gpoa == -1):
PR = -1
else:
PR = power/((self.ptc*gpoa)/self.gstc)
return round(PR,2)