-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlight_modulation_schedule.py
More file actions
581 lines (525 loc) · 35.6 KB
/
Copy pathlight_modulation_schedule.py
File metadata and controls
581 lines (525 loc) · 35.6 KB
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Author: Gregoire Vandenschrick
# Date: 20/09/2023
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# -- Import for the generation of data_points and communications
import os
import logging
import light_modulation_library_generation as lmlg
import light_modulation_library_communication as lmlc
import light_modulation_library_plot as lmlp
# ------------------------------------------------------------------------------
# -- local constants definitions.
PLOT = False
# ------------------------------------------------------------------------------
# -- Generation of schedules ---------------------------------------------------
"""
Here we will generate the schedule(s) that are to be modulated according to
the day in the year. Each schedule is basically the positive half part of a
cosine curve, set to begin at a given time and end at another and reach a
given maximum. begin and end are computed from the latitude-longitude given
in the light_modulation_settings.py file. Maximum is reached at the Summer
solstice, minimum at the Winter solstice. A small transition period at begin
and end is added according to a given duration to mimic dawn and dusk twilights.
Here we define three schedules, one being produced by adding one to
another, to achieve light lit only at the begin and end of the day, to
modulate the spectrum, again to mimic dawn and dusk light composition.
For easier reading, we encapsulate all this in function generate_schedules().
"""
# - Old methodology ------------------------------------------------------------
def generate_3500K_schedule(date, schedule_name, driver_maximum_intensity, maximum_intensity_required):
"""
Generate a curve for 3500k for dawn and dusk by summing two different schedules
One can pass to the generating functions the proportion of dawn and dusk
vs the length of the current day; by default = 0.25.
"""
# Parameters (you can adjust these)
maximum_voltage = 10 * (maximum_intensity_required/driver_maximum_intensity) # Maximum voltage (adjustable, 0-10V)
transition_duration_minutes = 45 # Duration of smooth transitions at the begin and end (x minutes)
# A first curve is generated for dawn, of a duration of 25% of the current day duration
(data_points_seconds_first,
junk,
daily_earliest_power_on,
junk,
daily_maximum_intensity) = lmlg.create_intensity_data_suntime(maximum_voltage,
date=date,
mode="dawn",
length_proportion=0.23,
shift_proportion=0.04,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 3,
plot=PLOT)
# We save back the schedule name and the modulated begin and max intensity
schedule_3500_dic = {
'schedule_name': schedule_name,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': daily_earliest_power_on,
'daily_maximum_intensity': daily_maximum_intensity
}
# A second curve is generated for dusk, of a duration of 30% of the current day duration
(data_points_seconds_second,
junk,
junk,
daily_latest_power_off,
junk) = lmlg.create_intensity_data_suntime(maximum_voltage*0.95,
date=date,
mode='dusk',
length_proportion=0.25,
shift_proportion=-0.04,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 4,
plot=PLOT)
# the two curves for dawn and dusk schedules are added
data_points_seconds = lmlg.sum_data_points_seconds(data_points_seconds_first,
data_points_seconds_second)
# We save back the data and the modulated end
schedule_3500_dic['full_schedule'] = data_points_seconds
schedule_3500_dic['latest_power_off'] = lmlg.get_equinox_sunset()
schedule_3500_dic['daily_latest_power_off'] = daily_latest_power_off
schedule_3500_dic['transition_duration_minutes'] = transition_duration_minutes
schedule_3500_dic['maximum_voltage'] = maximum_voltage
return schedule_3500_dic
def generate_5000K_schedule(date, schedule_name, driver_maximum_intensity, maximum_intensity_required):
"""
Generate total envelope curve for 5000k
"""
# Parameters (you can adjust these)
maximum_voltage = 10 * (maximum_intensity_required/driver_maximum_intensity) # Maximum voltage (adjustable, 0-10V)
transition_duration_minutes = 0 # Duration of smooth transitions at the begin and end (x minutes)
# A simple curve is generated for day, of a duration of 95% of the current day duration
(data_points_seconds,
junk,
daily_earliest_power_on,
daily_latest_power_off,
daily_maximum_intensity) = lmlg.create_intensity_data_suntime(maximum_voltage,
date=date,
length_proportion=1.055,
shift_proportion=0.0,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 5,
plot=PLOT)
# We save back the data and the modulated begin, end and max intensity
schedule_5000_dic = {
'schedule_name': schedule_name,
'full_schedule': data_points_seconds,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': daily_earliest_power_on,
'latest_power_off': lmlg.get_equinox_sunset(),
'daily_latest_power_off': daily_latest_power_off,
'transition_duration_minutes': transition_duration_minutes,
'maximum_voltage': maximum_voltage,
'daily_maximum_intensity': daily_maximum_intensity
}
return schedule_5000_dic
def generate_385_schedule(date, schedule_name, driver_maximum_intensity, maximum_intensity_required):
"""
Generate total envelope curve for 385nm
"""
# Parameters (you can adjust these)
maximum_voltage = 10 * (maximum_intensity_required/driver_maximum_intensity) # Maximum voltage (adjustable, 0-10V)
transition_duration_minutes = 0
# A simple curve is generated centered on midday, of a duration of 70% of the current day duration
(data_points_seconds,
junk,
daily_earliest_power_on,
daily_latest_power_off,
daily_maximum_intensity) = lmlg.create_intensity_data_suntime(maximum_voltage,
date=date,
length_proportion=0.85,
shift_proportion=0.0,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 5,
plot=PLOT)
# We save back the data and the modulated begin, end and max intensity
schedule_385_dic = {
'schedule_name': schedule_name,
'full_schedule': data_points_seconds,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': daily_earliest_power_on,
'latest_power_off': lmlg.get_equinox_sunset(),
'daily_latest_power_off': daily_latest_power_off,
'transition_duration_minutes': transition_duration_minutes,
'maximum_voltage': maximum_voltage,
'daily_maximum_intensity': daily_maximum_intensity
}
return schedule_385_dic
def generate_660_schedule(date, schedule_name, driver_maximum_intensity, maximum_intensity_required):
"""
Generate a curve for 660nm for dawn and dusk by summing two different schedules
One can pass to the generating functions the proportion of dawn and dusk
vs the length of the current day; by default = 0.10.
"""
# Parameters (you can adjust these)
maximum_voltage = 10 * (maximum_intensity_required/driver_maximum_intensity) # Maximum voltage (adjustable, 0-10V)
transition_duration_minutes = 60 # Duration of smooth transitions at the begin and end (x minutes)
# A first curve is generated for dawn, of a duration of 25% of the current day duration
(data_points_seconds_first,
junk,
daily_earliest_power_on,
junk,
daily_maximum_intensity) = lmlg.create_intensity_data_suntime(maximum_voltage*0.8,
date=date,
mode="dawn",
length_proportion=0.10,
shift_proportion=0.05,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 3,
plot=PLOT)
# A second curve is generated for dusk, of a duration of 30% of the current day duration
(data_points_seconds_second,
junk,
junk,
daily_latest_power_off,
junk) = lmlg.create_intensity_data_suntime(maximum_voltage,
date=date,
mode='dusk',
length_proportion=0.15,
shift_proportion=-0.07,
transition_duration_minutes=transition_duration_minutes,
maximum_broadness = 3,
plot=PLOT)
# the two curves for dawn and dusk schedules are added
data_points_seconds = lmlg.sum_data_points_seconds(data_points_seconds_first,
data_points_seconds_second)
# We save back the data and the modulated begin, end and max intensity
schedule_660_dic = {
'schedule_name': schedule_name,
'full_schedule': data_points_seconds,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': daily_earliest_power_on,
'latest_power_off': lmlg.get_equinox_sunset(),
'daily_latest_power_off': daily_latest_power_off,
'transition_duration_minutes': transition_duration_minutes,
'maximum_voltage': maximum_voltage,
'daily_maximum_intensity': daily_maximum_intensity
}
return schedule_660_dic
def generate_schedules(date, debug=False):
"""
Here we define the maximum intensity the schedules should produce according to
the capability of the drivers used.
6 FLUXengines 3500K in serie are driven by an XLG-150-L set at 600mA (lower than max to deliver lower light to better approach dawn and dusk light).
6 FLUXengines 5000K in serie are driven by an XLG-150-L set at 1050mA (max).
5 APEXengines 385nm in serie are driven by an LCM-40 set at 600mA.
You can adapt these settings according to your need and chains.
The XLG-150-L requires at least 0.79V to generate modulated current.
The LCM-40 requires at least 0.75V to generate modulated current.
These voltage were derived from testing and could be different from driver to driver.
They are needed to produce schedule values that are effectively generating light output.
"""
# --------------------------------------------------------------------------
# Generation of schedule for FLUXengines 3500K
number_of_modules_in_serie_3500K = 6
driver_maximum_intensity_3500K = 610 # This is the maximum Amper your led driver can produce
driver_minimal_voltage_for_light_3500K = 0.75 # This is the minimal voltage dim signal the driver reacts to
maximum_intensity_required_3500K = driver_maximum_intensity_3500K*(500/610) # This is the maximum Amper you want the driver to deliver during the schedule
schedule_3500_dic = generate_3500K_schedule(date, "schedule_3500", driver_maximum_intensity_3500K, maximum_intensity_required_3500K)
# Gating the data so the lowest values produce already light.
# Depending on your led array and driver, you should adjust this
data_points_seconds_3500 = lmlg.gate_data_points_seconds(schedule_3500_dic['full_schedule'], lower_gate=driver_minimal_voltage_for_light_3500K, plot=PLOT)
# The result is cleaned from redundant values and trimmed down to 32 values
schedule_3500 = lmlg.clean_and_simplify_to_desired_points(data_points_seconds_3500, plot=PLOT)
# --------------------------------------------------------------------------
# Generation of schedule for FLUXengines 5000K
number_of_modules_in_serie_5000K = 6
driver_maximum_intensity_5000K = 1510 # This is the maximum Amper your led driver can produce
driver_minimal_voltage_for_light_5000K = 0.95 # This is the minimal voltage dim signal the driver reacts to
maximum_intensity_required_5000K = driver_maximum_intensity_5000K*0.75 # This is the maximum Amper you want the driver to deliver during the schedule
schedule_5000_dic = generate_5000K_schedule(date, "schedule_5000", driver_maximum_intensity_5000K, maximum_intensity_required_5000K)
# Remove light from 5000K that is already given by 3500K
# (hence the scaling! and the need to know relative drivers maximum intensity settings).
data_points_seconds_5000 = lmlg.substract_data_points_seconds(schedule_5000_dic['full_schedule'],
lmlg.scale_data_points_seconds(schedule_3500_dic['full_schedule'],
(maximum_intensity_required_3500K/maximum_intensity_required_5000K)))
# Gating the data so the lowest values produce already light.
# Depending on your led array and driver, you should adjust this
data_points_seconds_5000 = lmlg.gate_data_points_seconds(data_points_seconds_5000, lower_gate=driver_minimal_voltage_for_light_5000K, plot=PLOT)
# The result is cleaned from redundant values and trimmed down to 32 values
schedule_5000 = lmlg.clean_and_simplify_to_desired_points(data_points_seconds_5000, plot=PLOT)
# --------------------------------------------------------------------------
# Generation of schedule for APEXengines 385
number_of_modules_in_serie_385 = 5
driver_maximum_intensity_385 = 600 # This is the maximum Amper your led driver can produce
driver_minimal_voltage_for_light_385 = 0.71 # This is the minimal voltage dim signal the driver reacts to
maximum_intensity_required_385 = driver_maximum_intensity_385*0.6 # This is the maximum Amper you want the driver to deliver during the schedule
schedule_385_dic = generate_385_schedule(date, "schedule_385", driver_maximum_intensity_385, maximum_intensity_required_385)
# Gating the data so the lowest values produce already light.
# Depending on your led array and driver, you should adjust this
data_points_seconds_385 = lmlg.gate_data_points_seconds(schedule_385_dic['full_schedule'], lower_gate=driver_minimal_voltage_for_light_385, plot=PLOT)
# The result is cleaned from redundant values and trimmed down to 32 values
schedule_385 = lmlg.clean_and_simplify_to_desired_points(data_points_seconds_385, plot=PLOT)
# --------------------------------------------------------------------------
# Generation of schedule for APEXengines 660 MKIII
number_of_modules_in_serie_660 = 5
driver_maximum_intensity_660 = 510 # This is the maximum Amper your led driver can produce
driver_minimal_voltage_for_light_660 = 0.9 # This is the minimal voltage dim signal the driver reacts to
maximum_intensity_required_660 = driver_maximum_intensity_660*0.30 # This is the maximum Amper you want the driver to deliver during the schedule
schedule_660_dic = generate_660_schedule(date, "schedule_660", driver_maximum_intensity_660, maximum_intensity_required_660)
# Gating the data so the lowest values produce already light.
# Depending on your led array and driver, you should adjust this
data_points_seconds_660 = lmlg.gate_data_points_seconds(schedule_660_dic['full_schedule'], lower_gate=driver_minimal_voltage_for_light_660, plot=PLOT)
# The result is cleaned from redundant values and trimmed down to 32 values
schedule_660 = lmlg.clean_and_simplify_to_desired_points(data_points_seconds_660, plot=PLOT)
# --------------------------------------------------------------------------
# packing of all the schedules generated in a dictionary.
# !! Key values ARE THE NAMES OF THE SCHEDULES DEFINED IN THE CRESCONTROL !!
# !! Second element in tuple is the out name for the schedule to modulate !!
# !! Third element is the meta info for Cre.Science HUB.
meta_3500 = 'out-a:meta="{\\"unit\\":\\"A\\",\\"module\\":{\\"driver\\":{\\"configuration\\":[' + f'{number_of_modules_in_serie_3500K:1d}' + ',1],\\"id\\":\\"MW-XLG-150-LAB\\"},\\"id\\":\\"CSC-FXE-140-C-35\\"},\\"type\\":\\"light\\",\\"icon\\":\\"sysFluxEngine\\",\\"id\\":\\"CSC-FXE-140-C-35\\",\\"name\\":\\"FXengine 3500K\\",\\"ecos\\":[\\"Lithops\\"],\\"curr\\":' + f'{(driver_maximum_intensity_3500K/1000):0.2f}' + '}"'
meta_5000 = 'out-b:meta="{\\"unit\\":\\"A\\",\\"module\\":{\\"driver\\":{\\"configuration\\":[' + f'{number_of_modules_in_serie_5000K:1d}' + ',1],\\"id\\":\\"MW-XLG-150-LAB\\"},\\"id\\":\\"CSC-FXE-140-C-50\\"},\\"type\\":\\"light\\",\\"icon\\":\\"sysFluxEngine\\",\\"id\\":\\"CSC-FXE-140-C-50\\",\\"name\\":\\"FXengine 5000K\\",\\"ecos\\":[\\"Lithops\\"],\\"curr\\":' + f'{(driver_maximum_intensity_5000K/1000):0.2f}' + '}"'
meta_385 = 'out-c:meta="{\\"unit\\":\\"A\\",\\"module\\":{\\"driver\\":{\\"configuration\\":[' + f'{number_of_modules_in_serie_385:1d}' + ',1],\\"id\\":\\"MW-LCM-040\\"},\\"id\\":\\"CSC-AXE-004-A-38\\"},\\"type\\":\\"light\\",\\"icon\\":\\"sysApexEngine\\",\\"id\\":\\"CSC-AXE-004-A-38\\",\\"name\\":\\"APEXengine 385 \\",\\"ecos\\":[\\"Lithops\\"],\\"curr\\":' + f'{(driver_maximum_intensity_385/1000):0.2f}' + '}"'
meta_660 = 'out-d:meta="{\\"unit\\":\\"A\\",\\"module\\":{\\"driver\\":{\\"configuration\\":[' + f'{number_of_modules_in_serie_660:1d}' + ',1],\\"id\\":\\"MW-XLG-025-XAB\\"},\\"id\\":\\"CSC-AXE-004-C-66\\"},\\"type\\":\\"light\\",\\"icon\\":\\"sysApexEngine\\",\\"id\\":\\"CSC-AXE-004-C-66\\",\\"name\\":\\"APEXengine 660 \\",\\"ecos\\":[\\"Lithops\\"],\\"curr\\":' + f'{(driver_maximum_intensity_660/1000):0.2f}' + '}"'
schedule_dic = {
"schedule_3500" : (schedule_3500, "out-a", meta_3500),
"schedule_5000" : (schedule_5000, "out-b", meta_5000),
"schedule_385" : (schedule_385, "out-c", meta_385),
"schedule_660" : (schedule_660, "out-d", meta_660)
}
color_dic = {
"schedule_3500" : 'ivory',
"schedule_5000" : 'lightSkyBlue',
"schedule_385" : 'blueviolet',
"schedule_660" : 'DarkRed'
}
# --------------------------------------------------------------------------
# get the json files for the modules
json_3500 = lmlg.get_module_json("fluxengine_3500k")
json_5000 = lmlg.get_module_json("fluxengine_5000k")
json_385 = lmlg.get_module_json("apexengine_385")
json_660 = lmlg.get_module_json("apexengine_660")
lit_area = 0.4 #m²
loss_factor = 0.8 #%
dli_3500 = loss_factor*number_of_modules_in_serie_3500K*lmlg.get_dli_by_m2(schedule_3500, driver_maximum_intensity_3500K, json_3500, lit_area)/1000000
dli_5000 = loss_factor*number_of_modules_in_serie_5000K*lmlg.get_dli_by_m2(schedule_5000, driver_maximum_intensity_5000K, json_5000, lit_area)/1000000
dli_385 = loss_factor*number_of_modules_in_serie_385*lmlg.get_dli_by_m2(schedule_385, driver_maximum_intensity_385, json_385, lit_area) /1000000
dli_660 = loss_factor*number_of_modules_in_serie_660*lmlg.get_dli_by_m2(schedule_660, driver_maximum_intensity_660, json_660, lit_area) /1000000
dli_details = f'\
DLI for the schedules calculated based on modules numbers, \n\
characteristics and driver settings for a lit area of {lit_area}m² \n\
and a loss factor of {((1.0-loss_factor)*100):2.0f}% \n\
--------------------------------------------------------------------------------\n\
{dli_3500:6.3f} mol/m²/day of photon delivered by 6 FLUXengines 3500K on a driver set at {driver_maximum_intensity_3500K:4.0f}mA. \n\
{dli_5000:6.3f} mol/m²/day of photon delivered by 6 FLUXengines 5000K on a driver set at {driver_maximum_intensity_5000K:4.0f}mA. \n\
{dli_385:6.3f} mol/m²/day of photon delivered by 5 APEXengines 385nm on a driver set at {driver_maximum_intensity_385:4.0f}mA. \n\
{dli_660:6.3f} mol/m²/day of photon delivered by 5 APEXengines 660nm on a driver set at {driver_maximum_intensity_660:4.0f}mA. \n\
{(dli_3500+dli_5000+dli_385+dli_660):6.3f} mol/m²/day of photon delivered in total.\n\
--------------------------------------------------------------------------------\n'
# --------------------------------------------------------------------------
# Create a schedules plot and an animated spectrum for the current day
if debug is True:
schedules_json_driver_dic = {
"schedule_3500" : (schedule_3500, json_3500, driver_maximum_intensity_3500K, number_of_modules_in_serie_3500K),
"schedule_5000" : (schedule_5000, json_5000, driver_maximum_intensity_5000K, number_of_modules_in_serie_5000K),
"schedule_385" : (schedule_385, json_385, driver_maximum_intensity_385, number_of_modules_in_serie_385),
"schedule_660" : (schedule_660, json_660, driver_maximum_intensity_660, number_of_modules_in_serie_660)
}
lmlp.create_plot(schedules_json_driver_dic, color_dic, date, timing=10, save_path="./schedules.png")
lmlp.animate_daily_spectrum(schedules_json_driver_dic, save_path="./spectrum_animation.mp4")
# --------------------------------------------------------------------------
# Shedules need to be passed as string to the Crescontrol.
schedule_dic = lmlg.stringify_schedules_in_dic(schedule_dic)
# generate a report of schedules generated to be sent by email (or simply printed)
schedules_dic_list = [schedule_3500_dic, schedule_5000_dic, schedule_385_dic, schedule_660_dic]
result_for_mail = generate_result_for_email(schedules_dic_list, complementary_text=dli_details)
return schedule_dic, result_for_mail
# - New methodology ------------------------------------------------------------
def generate_schedule(date, schedules_dic, schedule_name, debug=False):
"""
"""
schedule_dic = schedules_dic[schedule_name]
if "composed" in schedule_dic.keys():
logging.debug(f'Composed...')
# if the schedule is a composition of schedules
schedule = generate_composed_schedule(date, schedules_dic, schedule_name, debug=debug)
logging.debug(f'Composed {schedule_name} finished')
else:
logging.debug(f'Simple...')
# We generate a simple schedule
schedule = generate_simple_schedule(date, schedules_dic, schedule_name, debug=debug)
logging.debug(f'Simple {schedule_name} finished')
return schedule
def generate_simple_schedule(date, schedules_dic, schedule_name, debug=False):
"""
"""
schedule_dic = schedules_dic[schedule_name]
maximum_voltage = 10 * schedule_dic["maximum_intensity_required"] * schedule_dic["maximum_voltage_proportion"]
(data_points_seconds,
junk,
daily_earliest_power_on,
daily_latest_power_off,
daily_maximum_intensity) = lmlg.create_intensity_data_suntime(maximum_voltage,
date=date,
mode=schedule_dic["mode"],
length_proportion=schedule_dic["length_proportion"],
shift_proportion=schedule_dic["shift_proportion"],
transition_duration_minutes=schedule_dic["transition_duration_minutes"],
maximum_broadness=schedule_dic["maximum_broadness"],
plot=PLOT)
schedule_dic = {
'schedule_name': schedule_name,
'full_schedule': data_points_seconds,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': daily_earliest_power_on,
'latest_power_off': lmlg.get_equinox_sunset(),
'daily_latest_power_off': daily_latest_power_off,
'transition_duration_minutes': schedule_dic["transition_duration_minutes"],
'maximum_voltage': maximum_voltage,
'daily_maximum_intensity': daily_maximum_intensity
}
return schedule_dic
def generate_composed_schedule(date, schedules_dic, schedule_name, debug=False):
"""
"""
schedule_dic = schedules_dic[schedule_name]
schedules = []
ponderation_factors = []
for schedule_name_in_list in schedule_dic["composed"]["list"]:
# recursive call to generate the schedules required to compose the schedule
#if isinstance(schedule_name_in_list, dict):
schedules.append(generate_schedules_new(date, schedules_dic, schedule_name=schedule_name_in_list, debug=debug))
ponderation_factors.append(schedules_dic[schedule_name_in_list]["maximum_intensity_required"] * schedules_dic[schedule_name_in_list]["driver_maximum_intensity"])
if schedule_dic["composed"]["operation"] == "sum":
# A sum should be done between two schedules with the same modules/drivers characteristics.
# If not, the second schedule should be scaled accordingly (NOT THE CASE CURRENTLY, see next elif for implementation)
full_schedule = lmlg.sum_data_points_seconds(schedules[0]['full_schedule'], schedules[1]['full_schedule'])
elif schedule_dic["composed"]["operation"] == "diff":
# A substraction is usually conducted to retract from one schedule the light
# produced by another, not of the same modules/drivers characteristics.
# Hence the scaling of the second by the ratio of the maximum_intensity_required of each schedule.
full_schedule = lmlg.substract_data_points_seconds(schedules[0]['full_schedule'], lmlg.scale_data_points_seconds(schedules[1]['full_schedule'], ponderation_factors[1]/ponderation_factors[0]))
schedule_dic = {
'schedule_name': schedule_name,
'full_schedule': full_schedule,
'earliest_power_on': lmlg.get_equinox_sunrise(),
'daily_earliest_power_on': min(schedules[0]['daily_earliest_power_on'], schedules[1]['daily_earliest_power_on']),
'latest_power_off': lmlg.get_equinox_sunset(),
'daily_latest_power_off': max(schedules[0]['daily_latest_power_off'], schedules[1]['daily_latest_power_off']),
'transition_duration_minutes': max(schedules[0]['transition_duration_minutes'], schedules[1]['transition_duration_minutes']),
'maximum_voltage': schedules[0]['maximum_voltage'],
'daily_maximum_intensity': schedules[0]['daily_maximum_intensity']
}
return schedule_dic
def generate_schedules_new(date, schedules_dic, schedule_name=None, debug=False):
"""
This function generates all the schedules defined in the schedules dictionary.
This dictionary is imported from a json file where all the parameters are kept.
See light_modulation.json for more details.
"""
out_schedule_dic = {}
color_dic = {}
schedule_dic_list = []
schedules_json_driver_dic = {}
schedules_dic_to_treat = {}
# if we receive no schedule_name, we begin by the schedules to output (with a "name" key).
# This should be the case during the first and general call. The function is
# called recursively with a schedule_name defined.
if schedule_name is None:
logging.debug(f'0- Schedule_name is None')
for schedule_name, schedule_dic in schedules_dic.items():
if "name" in schedule_dic.keys():
logging.debug(f'0- Name in schedule_dic: {schedule_name}')
schedules_dic_to_treat[schedule_name] = schedules_dic[schedule_name]
else:
logging.debug(f'1- Schedule_name: {schedule_name}')
schedules_dic_to_treat[schedule_name] = schedules_dic[schedule_name]
# We treat all (or only one) schedules
schedule = {}
schedule_out = []
for name, schedule_dic in schedules_dic_to_treat.items():
logging.debug(f'Name to treat: {name}')
schedule = generate_schedule(date, schedules_dic, name, debug=debug)
if (schedule_name is not None) and len(schedules_dic_to_treat.keys()) == 1:
logging.debug(f'{schedule_name}, Schedule_name is not None: {schedule_name is not None}')
# the simple schedule is to be used to calculate another schedule
# return from a recursive call
return schedule
else:
logging.debug(f'Schedule_name is not None: {schedule_name is not None}')
# the simple schedule is a final one to output
schedule_out = lmlg.clean_and_simplify_to_desired_points(lmlg.gate_data_points_seconds(schedule['full_schedule'], lower_gate=schedule_dic["driver_minimal_voltage_for_light"], plot=PLOT), plot=PLOT)
out_schedule_dic[name] = (schedule_out, schedule_dic["out"], schedule_dic["meta"])
# keep schedules and details in structures for output
out_schedule_dic[name] = (schedule_out, schedule_dic["out"], generate_meta(schedule_dic))
# for plots
color_dic[name] = schedule_dic["plot_color"]
schedules_json_driver_dic[name] = (schedule_out, lmlg.get_module_json(schedule_dic["json"]), schedule_dic["driver_maximum_intensity"], schedule_dic["number_of_modules_in_serie"], schedule_dic["number_of_modules_in_parallel"])
# for mail report
schedule_dic_list.append(schedule)
# generation of documentations
dli_details = generate_dli_details(schedules_dic_to_treat, out_schedule_dic)
result_for_mail = generate_result_for_email(schedule_dic_list, complementary_text=dli_details)
# generation of plots TODO: change the arg debug to follow better switchargs...
if debug is True:
lmlp.create_plot(schedules_json_driver_dic, color_dic, date, timing=10, save_path="./schedules.png")
lmlp.animate_daily_spectrum(schedules_json_driver_dic, save_path="./spectrum_animation.mp4")
return lmlg.stringify_schedules_in_dic(out_schedule_dic), result_for_mail
# ------------------------------------------------------------------------------
def generate_result_for_email(schedule_dic_list, complementary_text=""):
result_for_mail = '\
Daily report of led lights schedules modulation\n\
--------------------------------------------------------------------------------\n\
Current day and time on %s: %s\n\
--------------------------------------------------------------------------------\n\
' % (f'{lmlc.get_local_ip()}',
f'{lmlg.datetime.datetime.now():%d %b %Y - %H:%M:%S}')
for schedule_dic in schedule_dic_list:
result_for_mail += '\
Schedule for %s\n\
begin: %s (time modulation: %s)\n\
end: %s (time modulation: %s)\n\
max: %sV (int. modulation: %s%%)\n\n\
' % (schedule_dic['schedule_name'],
"%02dh%02d" % (schedule_dic['daily_earliest_power_on'], (schedule_dic['daily_earliest_power_on']*60)%60),
lmlc.format_time_modulation_delta(schedule_dic['daily_earliest_power_on'], schedule_dic['earliest_power_on'], "%02dh%02dm"),
"%02dh%02d" % (schedule_dic['daily_latest_power_off'], (schedule_dic['daily_latest_power_off']*60)%60),
lmlc.format_time_modulation_delta(schedule_dic['daily_latest_power_off'], schedule_dic['latest_power_off'], "%02dh%02dm"),
"%05.2f" % (schedule_dic['maximum_voltage']*schedule_dic['daily_maximum_intensity']),
"%02.2f" % ((schedule_dic['daily_maximum_intensity'])*100.0))
result_for_mail += '\
--------------------------------------------------------------------------------\n'
result_for_mail += complementary_text
return result_for_mail
def generate_dli_details(schedules_dic_to_treat, out_schedule_dic):
"""
"""
lit_area = 0.4 #m²
loss_factor = 0.8 #%
total_dli = 0
dli_details = f'\
DLI for the schedules calculated based on modules numbers, \n\
characteristics and driver settings for a lit area of {lit_area}m² \n\
and a loss factor of {((1.0-loss_factor)*100):2.0f}% \n\
--------------------------------------------------------------------------------\n'
for schedule_name, schedule_dic in schedules_dic_to_treat.items():
# get the json files for the modules
json = lmlg.get_module_json(schedule_dic["json"])
number_of_modules_in_serie = schedule_dic["number_of_modules_in_serie"]
number_of_modules_in_parallel = schedule_dic["number_of_modules_in_parallel"]
number_of_modules = number_of_modules_in_serie*number_of_modules_in_parallel
schedule = out_schedule_dic[schedule_name][0]
driver_maximum_intensity = schedule_dic["driver_maximum_intensity"]
dli = loss_factor*number_of_modules*lmlg.get_dli_by_m2(schedule, driver_maximum_intensity/number_of_modules_in_parallel, json, lit_area)/1000000
total_dli += dli
dli_details += f'\
{dli:6.3f} mol/m²/day of photon delivered by {schedule_dic["json"]} configured as [{number_of_modules_in_serie},{number_of_modules_in_parallel}] on a driver set at {driver_maximum_intensity:4.0f}mA.\n'
dli_details += f'\
{total_dli:6.3f} mol/m²/day of photon delivered in total.\n\
--------------------------------------------------------------------------------\n'
return dli_details
def generate_meta(schedule_dic):
"""
"""
meta = schedule_dic["out"] +\
':meta="{\\"unit\\":\\"A\\",\\"module\\":{\\"driver\\":{\\"configuration\\":[' +\
f'{schedule_dic["number_of_modules_in_serie"]:1d},{schedule_dic["number_of_modules_in_parallel"]:1d}],\\"id\\":\\"' +\
f'{schedule_dic["driver"]}' + '\\"},\\"id\\":\\"' +\
f'{schedule_dic["module"]}' + '\\"},\\"type\\":\\"light\\",\\"icon\\":\\"' +\
f'{schedule_dic["module_type"]}' + '\\",\\"id\\":\\"' +\
f'{schedule_dic["module"]}' + '\\",\\"name\\":\\"' +\
f'{schedule_dic["eco_module_name"]}' +\
f'\\",\\"ecos\\":[\\"Lithops\\"],\\"curr\\":{(schedule_dic["driver_maximum_intensity"]/1000):0.2f}' + '}"'
return meta
# ------------------------------------------------------------------------------