-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathf1849856.py
More file actions
3583 lines (3118 loc) · 225 KB
/
Copy pathf1849856.py
File metadata and controls
3583 lines (3118 loc) · 225 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
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Initial draft solar reflector tracking code using photo resistors on Raspberry Pi
# NB- Must be used with GPIO 0.3.1a or later as earlier verions are not fast enough!
# So what's all this then?
# This code is being developed to control an array of solar reflectors to heat
# a home with low cost components, so making it economically appealing enough
# to be adopted an a large scale. The system may also be used for cooling with
# a simple condensor loop. When not used for heating or cooling for any decent
# duration the array should track the sun or at least be parked in a position
# to reflect heat back to the sky. Once adopted on a large scale besides saving
# vast amounts of CO2 emissions this extra heat reflected away from the earth
# should play a significant role in combatting climate change.
# Great, where's is at now?
# Currently this is the first software to fully autonomosly control a reflector,
# by simply plugging in the reflector and collector (ring of LDRs / PRs / photo
# resistors) and turning it on, it will search until it finds the sun and track
# it. All other known systems either require complex setup variables on start up
# (as in large scale solar power plants) or user intervention to locate the sun
# before starting tracking, or after the sun disapears behind clouds. The later
# just being used with a single reflector and similar sensor to this system's by
# some home hobbiests. See https://en.wikipedia.org/wiki/Heliostat for more info
# What's next?
# Use Baysian or other analysis to as accurately as possible control a reflector
# to track the sun. Then multiple reflectors can be added and manouvered in the
# same way. Later add multiple sensor capability and focusing to enable cooling
# of buildings, and with reflective tubes (as used for air conditioning) enable
# direct heating of water tank or even make cooking possibe! Also pole forecast
# to pre heat a home for cool nights, to be ported to android / ios for control
# by phone or tablet. The rest of this software should finally be ported to C++
# or micro python to run on ATmega (android type) chips for mass production.
# TODO-
# test- store calibration array and use when skipping or no sun, like cat5 cable type
# test- get sun from start position if located when calibrating (common to start on sun)
# do changemod once on startup then show log lines for debug instead of error
# move randomly between sensor calibration and health check, nb colides with axis
# update box seek to 4 corners and enable user input, and offer bolInterweave to orientate
# not seek when sun in, sleep at night etc
# when found sun on 'SeekSunIn' get min width and hight of sun and box search...
# around that until outside of sun, taking average positions when sensors see sun...
# to later use to calculate directions of motors in relation to movement on sensor.
# autoseeking stop, centre and verify coordinates, storing candidates
# recheck automatically if cable connected part way through sensor check
# use PWM on PR capacitor charging input?
# ad vertical and horizontal scrolling menus
# fix segmentation faults without try catch (max point 2/3 box 6 level 4)
# for release to calculate position of sun from time loose pyephem and use...
# https://en.wikipedia.org/wiki/Position_of_the_Sun
#
# Ecliptic coordinates (orbital plane):
# n is number of days (positive or negative) since Greenwich noon, 2000-1-1
# from the julian date that's:
# n = JD - 2451545
#
# mean longitude of the sun (corrected for aberration)
# L = 280.460deg + 0.9856474deg n
#
# mean anomaly of sun
# g = 357.528deg + 0.9856003deg n
#
# use modulus % 360 for L and g
#
# longitude of sun (ecliptic latitude of sun B is 0 - never exceeds 0.00033deg)
# y = L + (1.915deg * sin( g )) + (0.020deg * sin( 2 g ))
#
# obliquity of ecleptic
# e = 23.439deg - 0.0000004n
#
# Equatorial coordinates (equatorial plane):
# calculated from ecliptic variables above and e (the obliquity of the ecliptic)
#
# obliquity of ecleptic (angle between earth's rotational and orbital axis)
# e = 23.439deg - 0.0000004n
#
# a is ascension
# a = arctan((cos e) * (tan y))
# ... or in software...
# a = atan2((cos e) * (sin y), (cos y))
#
# d = arcsin((sin e) * (sin y))
# or aprox...
# d = arcsin((sin -23.44deg) * (sin y))
# d = -23.44deg * cos((360/365) * (N + 10))... 2deg err
# (N=0 at midnight Universal Time (UT) as January 1 begins (i.e. the days part of the ordinal date -1). The number 10, in (N+10), is the approximate number of days after the December solstice to January 1
# d = arcsin(sin(-23.44deg) * cos( ((360/365.24) * (N + 10)) + ( (360deg/pi) * 0.0167 * sin((360/365.24) * (N - 2)) ))... 2deg err
# d = -arcsin(0.39779 * cos( (0.98565 * (N + 10)) + (1.914deg * sin(0.98565 * (N - 2))) ))... 0.2deg err, 0.03deg err if 10 in N + 10 is adjusted to days after solstice from December 22
# also 0.1deg error morning / evening due to atmoshperic refraction
# for comparison sun is 0.5deg wide
#
# then make fuctions to do
# d = -23.44deg * cos((360/365) * (N + 10))... 2deg err
# a = atan2((cos e) * (sin y), (cos y))
# then get altazimuth by...
# use http://sciencing.com/calculate-solar-time-8612288.html to get 'local solar time'
# then get Solar zenith angle https://en.wikipedia.org/wiki/Solar_zenith_angle
# then get Solar azimuth angle https://en.wikipedia.org/wiki/Solar_azimuth_angle
# ie...
#def solarTimeMeridia():
#def solarTime():
#... etc
### set constants ###
# compilation
DEBUG = 1 # 1 for debug mode, 0 for release
#TIME_RC = True # measure photo resistor reading by time not loop counter
TIME_RC = False # NB- FAILS! always gives time 0.00012 to 00015 sun or not
AUTO_ZOOM = False # true to handle zoom automatically
SHOW_GRAPHS = False # true to show graphs (very slow, needs matplotlib too)
#SHOW_GRAPHS = Trues
USE_EPHEM = True # uses ephemeral astronomical data if set
DO_MOON_TOO = True # tracks moon at night instead of sun if true
AUTO_START = True # true if starting automatically on repower (requires setup)
SOUND_ON = True # true will sound beeps, false is silent
#SOUND_ON = False
# done below automagically now
#COL_CAT5_X = False # true if cat 5 cable for collector is crossed
#COL_CAT5_X = True
MOT_X_REV = False # true if motor 1 for turning is reveresed
#MOT_X_REV = True
MOT_Y_REV = False # true if motor 2 for tilting is reveresed
#MOT_Y_REV = True
USE_OK_BTN = True # true to use OK button (L+R together or seperate OK button)
LR_OK_BTNS = True # true to use left and right buttons pressed together as OK
TRACK_POS = True # true to count stepper motor steps, required for s/w limits
SW_LIMITS = True # true to enable software limit switches, stops motor overrun
#SW_LIMITS = False
LAZY_TRACK = False # true prevents software constantly writing position to file
LOG_DATA = True # true to log all data to periodic files
PR_CUTOFF = 100 # min PR val to start autotracking (ie when sun is out), full ambient sun is below 50, but can track around 100 through light cloud
#PR_CUTOFF = 2000 # crazy high for testing in dark
CALOBRATE = True # calibrate PRs to handle old sensors or ambient light
RECAL_ERR = 0.1 # forces recalibration if differences above this threshold
#RECAL_ERR = 0.15 # needed to pass calibration at night when calibration is crap
#RECAL_ERR = 0.05 # only passed occasionaly when really bright
PI_CONST = 3.14159 # maths pi constant, use import math, math.pi when need maths module
# general
CONFIG_FILE = "vinvar.json"
# current version
CODE_VERSION = 0.15
# previous version additions
# 0.1 - initial for john with lcd and stepper functionality only
# 0.11 - JSON config file storage and mid tracking halt and options
# 0.12 - non verbose text mode and scroll back and forth through setup
# 0.13 - self tests PRs and auto straight / crossover cable select
# 0.14 - global logging and autoseeking sun, fully autonomous :D
# 0.15 - TODO- PR location direction and use with auto tracking
# control
MAX_RC_TIME = 100000 # max PR reading, prevents hanging on broken circuit
PWM_MAX_HZ = 200 # max steps per second reflector moves (ie slew rate)
#PWM_MAX_HZ = 2000 # 2k Hz max clock impulses per second for motor control
#PWM_MAX_HZ = 800 # jumped badly
#PWM_MAX_HZ = 500 # REALLY jittery
#PWM_MAX_HZ = 300 # nearly nothing
#PWM_MAX_HZ = 450 # nothing
#PWM_MAX_HZ = 400 # works fine usually
#PWM_MAX_HZ = 350 # works like a charm (john just soldered something though)
#PWM_MAX_HZ = 5 # fine now
#PWM_MAX_HZ = 1 # just 1 Hz max pluses a second for testing motors
PWM_TRACK_HZ = 2 # rate of steps per second that reflector tracks sun
PWM_DUTY_CYCLE = 50 # percentage ratio of hi to low signal in each PWM pulse
#PWM_DUTY_CYCLE = 1
# set min thresholds to act on for various motors (smaller is more sensitive)
# (amounts)
# X_ACT_MIN = 50
# Y_ACT_MIN = 50
# Z_ACT_MIN = 10
# (differences)
#X_ACT_MIN = 0.5
#Y_ACT_MIN = 0.5
#Z_ACT_MIN = 0.5
# set all together
#ALL_ACT_MIN = 0.2 # 0.2 works great in the mornings, but careers off in the day:
ALL_ACT_MIN = 0.35
#ALL_ACT_MIN = 0.5 # 0.5 works in day but not so well in the mornings:
X_ACT_MIN = ALL_ACT_MIN
Y_ACT_MIN = ALL_ACT_MIN
Z_ACT_MIN = ALL_ACT_MIN
# import external code
import RPi.GPIO as GPIO # raspberry pi GPIO handling
import time
import os
# only use astronomical data if set to
if USE_EPHEM:
import ephem # for solar angles: 'sudo apt-get install python-dev' then 'sudo pip install pyephem'
import datetime # for UTC time required by ephem
# only import matplot lib if showing graphs as it's big n slow
if SHOW_GRAPHS:
import matplotlib.pyplot as plt
# for storing vars, pickle is usual for python, but json easier to port to c++
#import pickle # usual for python storing and retreiving variables to a file
#import cPickle as pickle # faster storing and retreiving variables
import json # similar to pickle, easier porting to c++
# Make it work for Python 2+3 and with Unicode
import io
#from builtins import chr # to make code python 2 and 3 compatable, but breaks 2 :(
try:
to_unicode = unicode
except NameError:
to_unicode = str
# set to Broadcom chip pin numbering
GPIO.setmode( GPIO.BCM )
# import LCD wrapper package
import vinlcd as Lcd
### set pin numbers ###
# buttons
PIN_BTN_LEFT = 5
PIN_BTN_RIGHT = 19
PIN_BTN_DOWN = 13
PIN_BTN_UP = 6
#PIN_BTN_OK = X
# array for processing
buttons = [PIN_BTN_UP, PIN_BTN_RIGHT, PIN_BTN_DOWN, PIN_BTN_LEFT]
#buttons = [PIN_BTN_UP, PIN_BTN_RIGHT, PIN_BTN_DOWN, PIN_BTN_LEFT, PIN_BTN_OK]
# array positions
aBTN_UP = 0
aBTN_RIGHT = 1
aBTN_DOWN = 2
aBTN_LEFT = 3
aBTN_OK = 4
# photo resistors
# PIN_PR_LEFT = 19
# PIN_PR_RIGHT = 26
# PIN_PR_UP = 20
# PIN_PR_DOWN = 21
# PIN_PR_INNER = 16
PIN_PR_LEFT = 16
PIN_PR_RIGHT = 12
PIN_PR_UP = 7
PIN_PR_DOWN = 20
PIN_PR_INNER = 21
PIN_PR_DEMAND = 26
# PIN_PR_Ref = 25 # TODO- add
# PIN_PR_Safe = 12 # TODO- add
# arrays for processing
PRs = [PIN_PR_UP, PIN_PR_RIGHT, PIN_PR_DOWN, PIN_PR_LEFT, PIN_PR_INNER, PIN_PR_DEMAND ]
PRvals = [None, None, None, None, None, None] # values
##PRavgs = [0, 0, 0, 0, 0, 0] # averages
##PRdifs = [0, 0, 0, 0, 0, 0] # differences
###PRerrs = [None, None, None, None, None, None] # average differences (using difs array for this now)
###PRadjs = [1, 1, 1, 1, 1, 1] # multipliers
##PRcals = [1, 1, 1, 1, 1, 1] # calibration variables
PRavgs = [0, 0, 0, 0, 0] # averages
PRdifs = [0, 0, 0, 0, 0] # differences
#PRerrs = [None, None, None, None, None] # average differences (using difs array for this now)
#PRadjs = [1, 1, 1, 1, 1] # multipliers
PRcals = [1, 1, 1, 1, 1] # calibration variables
PRtime = None
# array positions
aPR_UP = 0
aPR_RIGHT = 1
aPR_DOWN = 2
aPR_LEFT = 3
aPR_INNER = 4
aPR_DEMAND = 5
# swap crossed over lines if collector cat 5 cable is crossed
# NB: with current setup can sense automatically if Y+ is huge!
# - needs 6 capacitors though or will get 0 not max charge time
# done below now automagically
##if COL_CAT5_X:
##
## # crossover orange/green and white line
## intPrTemp = PIN_PR_DEMAND
## PIN_PR_DEMAND = PIN_PR_UP
## PIN_PR_UP = intPrTemp
##
## # crossover plain orange/green line
## intPrTemp = PIN_PR_INNER
## PIN_PR_INNER = PIN_PR_LEFT
## PIN_PR_LEFT = intPrTemp
# display
#PIN_LCD_RS = 14
#PIN_LCD_E = 15
#PIN_LCD_D4 = 23
#PIN_LCD_D5 = 24
#PIN_LCD_D6 = 25
#PIN_LCD_D7 = 8
# motors, pump and valve
# PIN_X_MOTOR_N = 5
# PIN_X_MOTOR_P = 6
# PIN_Y_MOTOR_N = 8
# PIN_Y_MOTOR_P = 7
# PIN_VALVE = 9
# PIN_PUMP = 10
PIN_X_MOTOR_N = 17
PIN_X_MOTOR_P = 22
PIN_Y_MOTOR_N = 27
PIN_Y_MOTOR_P = 11
PIN_VALVE = 10
PIN_PUMP = 9
# display backlight
LCD_LED = 4
# buzzer
PIN_BUZZER = 18
### initialise GPIO pins ###
# set buttons as inputs
GPIO.setup( PIN_BTN_LEFT, GPIO.IN )
GPIO.setup( PIN_BTN_RIGHT, GPIO.IN )
GPIO.setup( PIN_BTN_DOWN, GPIO.IN )
GPIO.setup( PIN_BTN_UP, GPIO.IN )
# set motors, pump and valve as outputs
GPIO.setup( PIN_X_MOTOR_N, GPIO.OUT )
GPIO.setup( PIN_X_MOTOR_P, GPIO.OUT )
GPIO.setup( PIN_Y_MOTOR_N, GPIO.OUT )
GPIO.setup( PIN_Y_MOTOR_P, GPIO.OUT )
GPIO.setup( PIN_VALVE, GPIO.OUT )
GPIO.setup( PIN_PUMP, GPIO.OUT )
# set LCD backlight as output
GPIO.setup( LCD_LED, GPIO.OUT )
# set buzzer as output
GPIO.setup( PIN_BUZZER, GPIO.OUT )
# TODO- halt everything?
# GPIO.output( PIN_X_MOTOR_N, GPIO.LOW )
# GPIO.output( PIN_X_MOTOR_P, GPIO.LOW )
# GPIO.output( PIN_Y_MOTOR_N, GPIO.LOW )
# GPIO.output( PIN_Y_MOTOR_P, GPIO.LOW )
# GPIO.output( PIN_VALVE, GPIO.LOW )
# GPIO.output( PIN_PUMP, GPIO.LOW )
### initialise variables ###
# previous version run
#dblLastVsn = 0 # initialise to 0 so unknown always run
# modes
bolVerbose = None # display more help text (easier but slower) or less
bolDcMotors = None # send DC motor signals or stepper
bolTestOptions = False # don't show test options by default
# zoom
zActCount = 0 # number of actions (pumps + vavle opennings/zoom ins + outs)
zActFrom = 0 # last zoom action carried out from
zActMaxDif = 0 # maximum zoom difference of this zoom action
zMaxDif = 0 # last maximum achievable zoom difference
# configuration vars dictionary object
dicConfig = {}
# initialise coordinates if tracking
if TRACK_POS:
#intPoss = [0, 0]
intPoss = [None, None]
intLastTimes = [time.time(), time.time()]
intSunAt = [None, None]
maxEphem = [None, None]
minEphem = [None, None]
maxSun = [None, None]
minSun = [None, None]
# steps per full 360 rotation for each axis (should be pre-set in config file)
intStepsPerRotation = [None, None]
# stores scale and baseline for ephem to steps conversion for shadowing sun
intEphem2stepsRatio = [None, None]
intEphem2stepsBase = [None, None]
# remembers current speeds of motors for smooth acc/decelearation
intLastSpeeds = [0, 0]
# set X,Y positions / limits to unset
#if SW_LIMITS:
#intLimits = [0, 0]
intLimits = [None, None]
# limits aprox X 10,000, Y 6,000 for john's 30cm newtonian test reflector
# initialise to no ephemeral data
#global ephemBody
ephemBody = None
ephemViewer = None
### main code and function definitions ###
# toggle comments on 2 lines below and near &
# to enable or dissable error handling. enable
# for release or may get segmentation errors
#try:
if True:
#GPIO.setwarnings(False)
# writes the given string to screen if debugging
def vindebug( strDebug ):
print( strDebug )
# writes axis pos var into given file, returning true if set.
# NB- fails if called from lower user than original creator!
# https://stackoverflow.com/questions/5994840/how-to-change-the-user-and-group-permissions-for-a-directory-by-name
def setAxisPos( intAxis, intPos ):
bolPosSet = True # return flag set true if axis position set
# set axis position if tracking or using software limits
if (TRACK_POS or SW_LIMITS) and not LAZY_TRACK:
try:
with open("axis" + str(intAxis) + "pos.txt", 'w') as f:
# set file to anyone read and write
try:
os.chmod( "axis" + str(intAxis) + "pos.txt", 0o666 )
except:
vindebug( "axis" + str(intAxis) + " file belongs to other user so can't chmod" )
# write position
f.write('%d' % intPos)
except:
bolPosSet = False
return bolPosSet
# reads axis pos var into given varaible, returning true if set
def getAxisPos( intAxis ):
bolPosGot = False # return flag set true if axis position retrieved
# get axis position if tracking or using software limits
if TRACK_POS or SW_LIMITS:
try:
# open file containing variable
file = open("axis" + str(intAxis) + "pos.txt", 'r')
# read contents from it
strTemp = file.readline()
# if given variable is ok read to axis
intPoss[intAxis - 1] = int(strTemp)
# close the file
file.close()
# set to return true as axis position retrieved
bolPosGot = True
except:
intPoss[intAxis - 1] = None
return bolPosGot
# global functions- should be outside 'try' if called in error handlers
#######################
# general functions #
#######################
### general ###
# clean up before exiting
def CleanUp( ):
Lcd.Clear( )
Lcd.ShowStr( " Exiting, ", 1 )
Lcd.ShowStr( " Please wait ", 2 )
# all stop
SetMotor( 1, 0 )
SetMotor( 2, 0 )
SetZoom( 0 )
### returns true if given var set to false, none or true returns false
## def IsFalse bolToTest:
##
## # returns true if given var set to true, none or false returns false
## def IsTrue bolToTest:
## bolReturn = False # initialise to return false
##
## # only set to return true if not none or false
## if not bolToTest is None:
## if not bolToTest:
## bolReturn = True
##
## return bolReturn
# stop PWM motor signals if using stepper motors
# TODO finish defs above and use those if perform this logic elsewhere
if not bolDcMotors is None:
if not bolDcMotors:
pwmMotorXp.stop( )
pwmMotorYp.stop( )
# show user program ended
if AUTO_START:
Lcd.Clear( )
Lcd.ShowStr( "Exited. Repower ", 1 )
Lcd.ShowStr( " to restart. ", 2 )
else:
Lcd.Clear( )
Lcd.ShowStr( "Exited, restart ", 1 )
Lcd.ShowStr( " from console. ", 2 )
# clear GPIOs
GPIO.cleanup( ) # clear GPIOs for other processes
vindebug( "GPIOs cleaned" )
# update requested configuration variable and store in JSON config file
def SetConfigVar( strName, varValue ):
print( "Called SetConfigVar( " + str(strName) + ", " + str(varValue) + " )" )
global dicConfig
# set requested variable
dicConfig[strName] = varValue
# Write JSON config file
with io.open(CONFIG_FILE, 'w', encoding='utf8') as outfile:
str_ = json.dumps(dicConfig,
indent=4, sort_keys=True,
separators=(',', ':'), ensure_ascii=False)
outfile.write(to_unicode(str_))
# set file to anyone read and write
try:
os.chmod( CONFIG_FILE, 0o666 )
except:
vindebug( str(CONFIG_FILE) + " file belongs to other user so can't chmod" )
# sets requested configuration variable to given value if doesn't yet exist
def SetDefaultVar( strName, varValue ):
global dicConfig
# try getting requested variable
try:
tryValue = dicConfig[strName]
# if failed (ie probably doesn't exist) set to given value
except:
SetConfigVar( strName, varValue )
# gets configuration variables, returning true if found or false otherwise
def getConfigVars( strVarFile ):
global dicConfig
# try getting configuration variables from JSON file
#if True: # for testing
try:
with open(strVarFile) as data_file:
dicConfig = json.load(data_file)
# if succeeded read in last version run
#dblLastVsn = dicConfig["dblLastVsn"]
#vindebug( "got previous config data version " + str(dicConfig["dblLastVsn"]) + ", current version " + str(CODE_VERSION) )
vindebug( "got previous config data from file '" + str(strVarFile) + "'" )
# if no file make empty dictionary object
#else: # for testing
except:
dicConfig = {}
vindebug( "made new default config data" )
# if no file make it with initial values
## dicConfig = {'dblVersion': str(CODE_VERSION),
## 'dblVsnWelcomed': 0,
## 'dblVsnDisclaimed': 0,
## 'bolVerbose': None,
## 'bolDcMotors': None,
## 'dtmFirstRun': time.strftime("%Y-%m-%d %H:%M:%S"),
## 'intRuns': 0
## }
# NB- do nested / other data types like...
##data = {'a list': [1, 42, 3.141, 1337, 'help', u''],
## 'a string': 'bla',
## 'another dict': {'foo': 'bar',
## 'key': 'value',
## 'the answer': 42}}
# set default values (only updates if doesn't already exist ie for new variables on upgrade, or bizarely if called within function :/ )
# NB- dicConfig.setdefault( 'dblVersion', CODE_VERSION ) etc doesn't work within function for some reason
vindebug( "setting any unset variables to default" )
SetDefaultVar( 'dblVersion', CODE_VERSION )
SetDefaultVar( 'dblVsnWelcomed', 0 )
SetDefaultVar( 'dblVsnDisclaimed', 0 )
SetDefaultVar( 'bolVerbose', None )
SetDefaultVar( 'bolDcMotors', None )
SetDefaultVar( 'intStepsPerRotation', [None, None] )
SetDefaultVar( 'bolUtpXed', None )
SetDefaultVar( 'strCity', None )
SetDefaultVar( 'fltLong', None )
SetDefaultVar( 'fltLat', None )
SetDefaultVar( 'fltElev', None )
SetDefaultVar( 'minSun', [None, None] )
SetDefaultVar( 'maxSun', [None, None] )
SetDefaultVar( 'minEphem', [None, None] )
SetDefaultVar( 'maxEphem', [None, None] )
SetDefaultVar( 'dtmFirstRun', time.strftime("%Y-%m-%d %H:%M:%S") )
SetDefaultVar( 'intRuns', 0 )
# add software limits if processing
if SW_LIMITS:
SetDefaultVar( 'intLimits', [None, None] )
# return user firendly time string in hours and mins since given time
def GetTimeDif( dtmSince ):
# get seconds elapsed since given time
intSecsSince = time.time() - dtmSince
# speed up for testing
#intSecsSince *= 2555555
# initialise elapsed string to seconds
strTimeDif = str(intSecsSince) + " seconds"
# if less than 2 mins show min
if intSecsSince < 120:
# if less than 1 min show first min
if intSecsSince < 60:
strTimeDif = "1st minute"
else:
strTimeDif = "2nd minute"
# else if less than one hour show mins
elif intSecsSince < (60 * 60):
strTimeDif = str(int(intSecsSince / 60)) + " minutes"
# else show hours and mins
else:
# get hours and minutes elapsed since given time
intHoursSince = int(intSecsSince / (60 * 60))
intMinsSince = int((intSecsSince - (60 * 60 * intHoursSince)) / 60)
# if less than 24 hours show hours and mins
if intHoursSince < 24:
strTimeDif = str(intHoursSince) + "hrs " + str(intMinsSince) + "mins"
# else show days and time
else:
# get days elapsed
intDaysSince = int(intHoursSince / 24)
# set hours and minutes to remainders
intHoursSince = int(intHoursSince - (intDaysSince * 24))
#intMinsSince = int((intSecsSince - (60 * 60 * intHoursSince)) / 60)
# if less than 100 days show days and time elapsed
if intDaysSince < 100:
# initialise string to pad minutes if not double figures
if intMinsSince < 10:
strTimeDif = "0"
else:
strTimeDif = ""
# get minutes elapsed
strTimeDif = strTimeDif + str(intMinsSince)
# add days and hours
strTimeDif = str(intDaysSince) + "days " + str(intHoursSince) + ":" + strTimeDif
# else show months and days
else:
# get months elapsed
intMonthsSince = int(intDaysSince / 30)
# if less than 24 months show months and days elapsed
if intMonthsSince < 24:
# set days to remainder
intDaysSince = int(intDaysSince - (intMonthsSince * 30))
# show months and days elapsed
strTimeDif = str(intMonthsSince) + "mths " + str(intDaysSince) + "dys"
# else show years and days
else:
# get years elapsed
intYearsSince = int(intDaysSince / 365.25)
# set days to remainder
intDaysSince = int(intDaysSince - (intYearsSince * 365.25))
# show years and days elapsed
strTimeDif = str(intYearsSince) + "yrs " + str(intDaysSince) + "dys"
# return time difference
return strTimeDif
#####################
# input functions #
#####################
### LDR brightness ###
# return calibrated photo resistor reading from capacitor charging time
# and updates list. high number means darker, low number means lighter.
def PRval( intPRpos ):
MAX_READS = 2 # set max repeated PR reads if reading excesive
intReads = 0
# read a few times if value excesive
while intReads < MAX_READS:
# first get PR value
fltCalPrVal = RCtime(PRs[intPRpos])
#vindebug( "read " + str(intReads) + " for PRval " + str(intPRpos) + " raw value " + str(fltCalPrVal) )
# calibrate if required and not demand PR
if CALOBRATE and (intPRpos < aPR_DEMAND):
#vindebug( "callibrated raw value: " + str(fltCalPrVal) + ", by: " + str(PRcals[intPRpos]) )
fltCalPrVal = PRcals[intPRpos] * fltCalPrVal
# exit loop if not too different from the previous
# reading, else increase read count to check again
try:
#if abs((fltCalPrVal - PRvals[intPRpos]) / (PRvals[intPRpos] + 0.01)) < 0.2:
if abs((fltCalPrVal - PRvals[intPRpos]) / (PRvals[intPRpos] + 0.01)) < (0.2 + (0.0005 * fltCalPrVal)): # added portion of value to error ratio as larger values vary more
#print( "read " + str(intReads) + " done for PRval( " + str(intPRpos) + " ) = " + str(fltCalPrVal) )
break
else:
#print( "read " + str(intReads) + " outsided PRval( " + str(intPRpos) + " ) = " + str(fltCalPrVal) )
intReads += 1
BoxBeep( 0.05 )
#print( "rechecking excesive reading " + str(fltCalPrVal) + " on PR " + str(intPRpos) + ", was " + str(PRvals[intPRpos]) )
except:
intReads += 1
# add calibrated value to array
PRvals[intPRpos] = fltCalPrVal
# return calibrated PR value
#print( "Called PRval( " + str(intPRpos) + " ) = " + str(fltCalPrVal) )
return fltCalPrVal
# return photo resistor light readings from capacitor charging time
# - high number means darker, low number means lighter
def RCtime( RCpin ):
# zero counter and decharge capacitor before starting
GPIO.setup( RCpin, GPIO.OUT )
GPIO.output( RCpin, GPIO.LOW )
time.sleep( 0.1 )
# time RC if set to
if TIME_RC:
# set variables for charging
reading = float( MAX_RC_TIME )
bolTime = False
# get charge start time
try:
dtmPRstart = time.process_time()
except:
dtmPRstart = time.time()
# start charging capacitor
GPIO.setup( RCpin, GPIO.IN )
# count loops done until charged.
# This takes under 1ms per cycle
# NB- see if for loop to MAX_RC_TIME with break when high quicker
while (GPIO.input(RCpin) == GPIO.LOW) and (reading < MAX_RC_TIME):
bolTime = True
#bolTime = False
# show value for debug
# (done together below now)
#if not SHOW_GRAPHS:
# print( reading )
# get total charge time
try:
reading = time.process_time() - dtmPRstart
except:
reading = time.time() - dtmPRstart
print( "raw reading: " + str(reading) )
# make similar to previous format values, up to max
if reading < (MAX_RC_TIME / 100000):
reading = round(reading * float(100000), 2)
#reading = 1000 * reading
#print( "reading adjusted to " + str(reading) )
#reading = round(reading, 2)
print( "reading rounded to " + str(reading) )
else:
reading = float(MAX_RC_TIME)
print( "set to max" )
# else time by loop counters
else:
# initialise time counter
reading = 0
# start charging capacitor and count loops done until charged
GPIO.setup( RCpin, GPIO.IN )
# This takes about 1 millisecond per loop cycle
# NB- see if for loop to MAX_RC_TIME with break when high quicker
while (GPIO.input(RCpin) == GPIO.LOW) and (reading < MAX_RC_TIME):
reading += 1
# return value
return reading
# returns time for photo resistor to charge capitcitor and returns value
# as well as adding it to the given list for graphing display later
def RCgraphTime( RCpin, pinValues ):
# get reading
reading = RCtime( RCpin )
# add to given list and plot graph
pinValues.append( reading )
plt.plot( [i for i in range(intMainLoop)], pinValues )
# return reading
return reading
### buttons ###
# returns true if the given button GPIO was pressed
# waiting the given time to debounce (0 if no need)
#def btnPress(btnPin, msWait):
# #initialise a previous input variable to 0 (assume button not pressed last)
# prev_input = 0
# while True:
#
# #take a reading
# input = GPIO.input(17)
# #if the last reading was low and this one high, print
# if ((not prev_input) and input):
# print("Button pressed")
# #update previous input
# prev_input = input
# #slight pause to debounce
# time.sleep(0.05)
# returns true if given button is pressed
def BtnOn( intBtnToCheck ):
bolOn = False
# process if 0 or above
if intBtnToCheck > -1:
# return GPIO pin logic unless OK button, then check left and right pressed together
if intBtnToCheck < aBTN_OK:
bolOn = GPIO.input( buttons[intBtnToCheck] )
elif LR_OK_BTNS:
bolOn = (BtnOn( aBTN_LEFT ) and BtnOn( aBTN_RIGHT ))
# return true if button pressed
return bolOn
# waits for given time (forever if given -1)
# unless key pressed, return number of key pressed
def BtnWait( dblSecs ):
# wait for requested time
#time.sleep( dblSecs )
intBtnReturn = -1 # button value to return, default to none
# calculate time to stop
dtmStop = time.time( ) + dblSecs
# check for input until time elapsed
#while time.time( ) < dtmStop:
# changed above to Do-While structure, now exit from loop
while True:
# if input given set action and exit loop to end delay
#if GPIO.input(PIN_BTN_LEFT):
if BtnOn( aBTN_LEFT ):
intBtnReturn = Lcd.CHR_BTN_LEFT
break
elif BtnOn( aBTN_RIGHT ):
intBtnReturn = Lcd.CHR_BTN_RIGHT
break
elif BtnOn( aBTN_DOWN ):
intBtnReturn = Lcd.CHR_BTN_DOWN
break
elif BtnOn( aBTN_UP ):
intBtnReturn = Lcd.CHR_BTN_UP
break
# exit if reached stop time and not waiting forever
elif (dblSecs != -1) and (time.time( ) >= dtmStop):
break
# return button result
print( "Called BtnWait( " + str(dblSecs) + " ) = " + str(intBtnReturn) )
return intBtnReturn
# returns true if any button pressed
def AnyBtnPressed( ):
#return GPIO.input(PIN_BTN_LEFT) or GPIO.input(PIN_BTN_RIGHT) or GPIO.input(PIN_BTN_UP) or GPIO.input(PIN_BTN_DOWN)
return BtnOn( aBTN_UP ) or BtnOn( aBTN_RIGHT ) or BtnOn( aBTN_DOWN ) or BtnOn( aBTN_LEFT ) or BtnOn( aBTN_OK )
# waits given max seconds or until no switches pressed
def DebounceBtns( intMaxSecsWait = 4 ):
# calculate time to stop
dtmStop = time.time( ) + intMaxSecsWait
# check for input until time elapsed
while time.time( ) < dtmStop:
#print( "debounce Btns: Left " + str(GPIO.input(PIN_BTN_LEFT)) + "\tRight " + str(GPIO.input(PIN_BTN_RIGHT)) + "\tDown " + str(GPIO.input(PIN_BTN_DOWN)) + "\tUp " + str(GPIO.input(PIN_BTN_UP)) )
print( "debounce Btns: Left " + str(BtnOn( aBTN_LEFT )) + "\tRight " + str(BtnOn( aBTN_RIGHT )) + "\tDown " + str(BtnOn( aBTN_DOWN )) + "\tUp " + str(BtnOn( aBTN_UP )) + "\tOK " + str(BtnOn( aBTN_OK )) )
# sleep for a moment if any button still pressed
if AnyBtnPressed( ):
time.sleep( 0.05 )
# else no button pressed now so exit
else:
break
# sleep for another moment to ensure debounce
time.sleep( 0.05 )
######################
# output functions #
######################
### drive ###
# moves given motor 1(X) or 2(Y) in given direction
# -ve reverse or +ve forward, at given speed, or 0 to stop
def SetMotor( intMotor, intSpeed ):
#print( str(time.time()) + " Called SetMotor( " + str(intMotor) + ", " + str(intSpeed) + " ) for DC motors: " + str(bolDcMotors) )
bolMove = True # initialise flag to carry out movement
# set direction from sign of given speed
#if intSpeed > 0:
# intDirection = 1
#elif intSpeed < 0:
# intDirection = -1
#else:
# intDirection = 0
# reduce speed to max in either direction if too high
if abs(intSpeed) > PWM_MAX_HZ:
intSpeed = (intSpeed / abs(intSpeed)) * PWM_MAX_HZ
# get start time stamp
dtmTrackFrom = time.time()
# prcoess tracking or limits if set to
if TRACK_POS or SW_LIMITS:
# process coordinates if enabled
if not intPoss[intMotor-1] is None:
# this step counter system for position tracking works on averages,
# because the first instant creates 1 step, then one for each full
# pulse period after, the average system works by awarding 1/2 step
# if starting from 0, then time divided by pulse periods afterwards
if intLastSpeeds[intMotor-1] != 0:
# add previous movement to current position
# NB- ballsed up as actual PWM control only counts complete cycles maybe?
#intPoss[intMotor-1] = intPoss[intMotor-1] + (intLastSpeeds[intMotor-1] * (dtmTrackFrom - intLastTimes[intMotor-1]))
# get time in secs last movement was running
#print( "getting time as now: " + str(dtmTrackFrom) + " less Last Time: " + str(intLastTimes[intMotor-1]) )
fltLastRunSecs = dtmTrackFrom - intLastTimes[intMotor-1]
#print( " = dif of: " + str(fltLastRunSecs) )