-
Notifications
You must be signed in to change notification settings - Fork 0
/
Soulmate.py
180 lines (164 loc) · 6.79 KB
/
Soulmate.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
# sudo pigpio must be running for the code to function properly
import pigpio
from time import sleep
import requests
import math
from geopy import distance
class Soulmate:
def __init__(self):
''' GRID - list of coordinates within FOV '''
self.DIR = 20
self.STEP = 21
self.MODE = (14, 15, 18)
self.MASTER = 16
self.SWITCH = 17
self.CW = False
self.CCW = True
self.SPR = 6400
self.freq = 2000
self.res = '1/32'
self.azimuth = 0
self.RESOLUTION = {
'Full':(0,0,0),
'Half':(1,0,0),
'1/4':(0,1,0),
'1/8':(1,1,0),
'1/16':(0,0,1),
'1/32':(1,0,1)
}
self.GPS = (48.95,22.26)
self.GRID = self.generate_gps(self.GPS)
try:
# Connect to pigpio daemon
self.pi = pigpio.pi()
# Switch setup, 1 by default, 0 when pressed. Possible EMI in homing sequence
self.pi.set_mode(self.SWITCH, pigpio.OUTPUT)
self.pi.write(self.SWITCH, 1)
# Master setup, 0 by default, arming and disarming the stepper
self.pi.set_mode(self.MASTER, pigpio.OUTPUT)
self.pi.write(self.MASTER, 0)
# Microstepping mode pins initiate
for m in self.MODE:
self.pi.set_mode(m, pigpio.OUTPUT)
# PWM speed initiate
self.pi.set_PWM_frequency(self.STEP, self.freq)
for m, v in zip(self.MODE, self.RESOLUTION[str(self.res)]):
self.pi.write(m, v)
except:
print('Error occured while loading Pigpio Daemon')
def arm(self, master, dutycycle):
''' sets MASTER pin to HIGH/LOW, also, disabled stepper does not get hot '''
self.pi.set_PWM_dutycycle(self.STEP, dutycycle)
self.pi.write(self.MASTER, master)
def home(self):
''' homing sequence, redundant loop because of EMI '''
sleep(1)
for i in range(5):
self.arm(True, 128)
while self.pi.read(self.SWITCH):
self.pi.write(self.DIR, self.CCW)
sleep(.001)
self.arm(True, 0)
self.azimuth = 0
self.arm(False, 0)
def rotate(self, azimuth_actual):
''' rotates the camera to desired azimuth_actual '''
sleep(1)
if azimuth_actual == 0:
self.home()
elif self.azimuth < azimuth_actual < 255:
self.arm(True, 128)
for i in range(int((self.SPR / 360) * (azimuth_actual - self.azimuth))):
self.pi.write(self.DIR, self.CW)
sleep(.0017)
self.azimuth = azimuth_actual
elif azimuth_actual < self.azimuth and azimuth_actual < 255:
self.arm(True, 128)
for i in range(int((self.SPR / 360) * (self.azimuth - azimuth_actual))):
self.pi.write(self.DIR, self.CCW)
sleep(.0017)
self.azimuth = azimuth_actual
else:
pass
self.arm(True, 0)
def true_bearing(self, a, b):
''' a,b are tuples of coordinates (lat, lon), function returns bearing to b as seen from a, great circle method '''
a = [math.radians(i) for i in a]
b = [math.radians(i) for i in b]
s = math.cos(b[0]) * math.sin(abs(b[1] - a[1]))
c = math.cos(a[0]) * math.sin(b[0]) - math.sin(a[0]) * math.cos(b[0]) * math.cos(abs(b[1] - a[1]))
result = math.degrees(math.atan2(s, c))
return int((result + 360) % 360)
def true_distance(self, a, b):
''' a,b are tuples of coordinates (lat, lon), function returns their distance in km '''
return round(distance.geodesic(a, b).km, 2)
def generate_gps(self, gps):
''' creates and returns a list, grid of gps (lat, lon) tuples within a radius in km from initial gps coords. Temporary variables responsible for spacing of the final coord grid'''
positions = []
lat_it = 50
lon_it = 50
incr = .5
radius = 500
for lat in range(lat_it):
for lon in range(lon_it):
''' North-East '''
gps_new = (round(gps[0] + (lat * incr), 2), round(gps[1] + (lon * incr), 2))
if self.true_distance(gps, gps_new) < radius:
positions.append((gps_new[0], gps_new[1]))
for lat in range(lat_it):
for lon in range(lon_it):
''' South-East '''
gps_new = (round(gps[0] - (lat * incr), 2), round(gps[1] + (lon * incr), 2))
if self.true_distance(gps, gps_new) < radius:
positions.append((gps_new[0], gps_new[1]))
return positions
def storm_api_check(self, gps):
''' returns tuple containing True confirming parameter, weather status, 2 thunderstorm, 5** heavy rains '''
r = requests.get("https://api.openweathermap.org/data/2.5/weather?lat={}&lon={}&appid=53a4c66814d54b4d9cb3ff3b5c013d86".format(gps[0], gps[1])).json()
id = r['weather'][0]['id']
if id // 100 == 2 or id == 502 or id == 503 or id == 504:
return True
def sweep(self, grid):
''' returns a list of tuples ((lat, lon), bearing, distance) to the storms and rain respectively '''
results = []
print('Fetching API data... ')
for gps in grid:
try:
interim = self.storm_api_check(gps)
if interim:
results.append((gps, self.true_bearing(self.GPS, gps), self.true_distance(self.GPS, gps)))
except:
continue
print('Data fetched successfully.')
return results
def store_data(self):
''' how often store azimuth data, what format - UTC, bearing? '''
pass
def automatic(self):
''' rotates to every active storm in 5s intervals '''
self.home()
active = self.sweep(self.GRID)
if len(active) != 0:
for storm in active:
print('Processing data... ')
sleep(5)
print(f'==== lat: {storm[0][0]}, lon: {storm[0][1]}, Bearing: {storm[1]} deg, Distance: {storm[2]} km ====')
print(storm[0])
self.rotate(storm[1])
else:
print('No current active storms within the FOV')
print('Finished with all instances... going home...')
self.home()
def manual(self):
''' custom coordinates '''
self.home()
look_at = [90, 180, 45, 135, 10]
for log in look_at:
sleep(1)
self.rotate(log)
self.arm(False, 0)
def service(self):
''' dev '''
while 1:
print(self.pi.read(self.SWITCH))
sleep(.25)