solareclipseworkbench 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ from solareclipseworkbench.notifications import voice_prompt
2
+ from solareclipseworkbench.camera import take_picture
3
+ from solareclipseworkbench.camera import take_burst
4
+ from solareclipseworkbench.camera import take_bracket
5
+ from solareclipseworkbench.commands import execute_command
6
+ from solareclipseworkbench.gui import sync_cameras
7
+
8
+ __all__ = ["voice_prompt", "take_picture", "sync_cameras", "take_burst", "take_bracket", "execute_command"]
@@ -0,0 +1,567 @@
1
+ import locale
2
+ import logging
3
+ import time
4
+
5
+ import gphoto2
6
+ import gphoto2 as gp
7
+ from datetime import datetime
8
+
9
+ from gphoto2 import Camera
10
+
11
+
12
+ class CameraError(Exception):
13
+ pass
14
+
15
+
16
+ class CameraSettings:
17
+
18
+ def __init__(self, camera_name: str, shutter_speed: str, aperture: str, iso: int):
19
+ """ Initialise new camera settings.
20
+
21
+ Args:
22
+ - camera_name: Name of the camera
23
+ - shutter_speed: Exposure time [s], e.g. "1/2000".
24
+ - aperture: Aperture (f-number), e.g. 5.6.
25
+ - iso: ISO-value.
26
+ """
27
+ self.camera_name = camera_name
28
+ self.shutter_speed = shutter_speed
29
+ self.aperture = aperture
30
+ self.iso = iso
31
+
32
+
33
+ def take_picture(camera: Camera, camera_settings: CameraSettings) -> None:
34
+ """ Take a picture with the selected camera
35
+
36
+ Args:
37
+ - camera_name: Camera object
38
+ - camera_settings: Settings of the camera (exposure, f, iso)
39
+ """
40
+
41
+ context, config = __adapt_camera_settings(camera, camera_settings)
42
+
43
+ # Take picture
44
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
45
+
46
+
47
+ def __adapt_camera_settings(camera, camera_settings):
48
+ context = gp.gp_context_new()
49
+ config = gp.check_result(gp.gp_camera_get_config(camera, context))
50
+ # Set ISO
51
+ if "Nikon" in camera_settings.camera_name:
52
+ gp.gp_widget_set_value(gp.check_result(gp.gp_widget_get_child_by_name(config, 'autoiso')), str("Off"))
53
+ # set config
54
+ gp.gp_camera_set_config(camera, config, context)
55
+
56
+ gp.gp_widget_set_value(gp.check_result(gp.gp_widget_get_child_by_name(config, 'iso')), str(camera_settings.iso))
57
+ # set config
58
+ gp.gp_camera_set_config(camera, config, context)
59
+ time.sleep(0.1)
60
+
61
+ # Set aperture
62
+ try:
63
+ if "Canon" in camera_settings.camera_name:
64
+ gp.gp_widget_set_value(gp.check_result(gp.gp_widget_get_child_by_name(config, 'aperture')),
65
+ str(camera_settings.aperture))
66
+ elif "Nikon" in camera_settings.camera_name:
67
+ gp.gp_widget_set_value(gp.check_result(gp.gp_widget_get_child_by_name(config, 'f-number')),
68
+ str(camera_settings.aperture))
69
+ # set config
70
+ gp.gp_camera_set_config(camera, config, context)
71
+ time.sleep(0.1)
72
+ except gphoto2.GPhoto2Error:
73
+ pass
74
+
75
+ # Set shutter speed
76
+ gp.gp_widget_set_value(gp.check_result(gp.gp_widget_get_child_by_name(config, 'shutterspeed')),
77
+ str(camera_settings.shutter_speed))
78
+ # set config
79
+ gp.gp_camera_set_config(camera, config, context)
80
+ time.sleep(0.1)
81
+
82
+ return context, config
83
+
84
+
85
+ def take_burst(camera: Camera, camera_settings: CameraSettings, duration: float) -> None:
86
+ """ Take a burst with the selected camera. For Canon, the duration is the duration in seconds, for Nikon, the
87
+ duration is the number of pictures to take.
88
+
89
+ Args:
90
+ - camera_name: Camera object
91
+ - camera_settings: Settings of the camera (exposure, f, iso)
92
+ - duration: Duration of the burst in seconds (Canon) or number of pictures (Nikon)
93
+ """
94
+ context, config = __adapt_camera_settings(camera, camera_settings)
95
+
96
+ # Take picture
97
+ if "Canon" in camera_settings.camera_name:
98
+ # Push the button
99
+ remote_release = gp.check_result(gp.gp_widget_get_child_by_name(config, 'eosremoterelease'))
100
+ gp.gp_widget_set_value(remote_release, "Press Full")
101
+ # set config
102
+ gp.gp_camera_set_config(camera, config, context)
103
+ time.sleep(duration)
104
+
105
+ # Release the button
106
+ remote_release = gp.check_result(gp.gp_widget_get_child_by_name(config, 'eosremoterelease'))
107
+ gp.gp_widget_set_value(remote_release, "Release Full")
108
+ # set config
109
+ gp.gp_camera_set_config(camera, config, context)
110
+ elif "Nikon" in camera_settings.camera_name:
111
+ # Push the button
112
+ capture_mode = gp.check_result(gp.gp_widget_get_child_by_name(config, 'capturemode'))
113
+ gp.gp_widget_set_value(capture_mode, "Burst")
114
+ # set config
115
+ gp.gp_camera_set_config(camera, config, context)
116
+
117
+ burst_number = gp.check_result(gp.gp_widget_get_child_by_name(config, 'burstnumber'))
118
+ gp.gp_widget_set_value(burst_number, round(duration))
119
+ # set config
120
+ gp.gp_camera_set_config(camera, config, context)
121
+
122
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
123
+
124
+
125
+ def take_bracket(camera: Camera, camera_settings: CameraSettings, steps: str) -> None:
126
+ """ Take a bracketing of images with the selected camera.
127
+
128
+ Args:
129
+ - camera_name: Camera object
130
+ - camera_settings: Settings of the camera (exposure, f, iso)
131
+ - steps: Steps for each bracketing step (e.g. +/- 1 2/3)
132
+ """
133
+ context, config = __adapt_camera_settings(camera, camera_settings)
134
+
135
+ if "Canon" in camera_settings.camera_name:
136
+ # Set aeb
137
+ aeb = gp.check_result(gp.gp_widget_get_child_by_name(config, 'aeb'))
138
+ gp.gp_widget_set_value(aeb, steps)
139
+ # set config
140
+ gp.gp_camera_set_config(camera, config, context)
141
+
142
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
143
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
144
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
145
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
146
+ camera.capture(gp.GP_CAPTURE_IMAGE, context)
147
+
148
+ # Set aeb
149
+ aeb = gp.check_result(gp.gp_widget_get_child_by_name(config, 'aeb'))
150
+ gp.gp_widget_set_value(aeb, "off")
151
+ # set config
152
+ gp.gp_camera_set_config(camera, config, context)
153
+
154
+
155
+ def mirror_lock(camera: Camera, camera_settings: CameraSettings) -> None:
156
+ """ Lock the mirror
157
+
158
+ Args:
159
+ - camera_name: Camera object
160
+ """
161
+ context = gp.gp_context_new()
162
+ config = gp.check_result(gp.gp_camera_get_config(camera, context))
163
+
164
+ if "Canon" in camera_settings.camera_name:
165
+ lock = gp.check_result(gp.gp_widget_get_child_by_name(config, 'mirrorlock'))
166
+ gp.gp_widget_set_value(lock, "1")
167
+ # set config
168
+ gp.gp_camera_set_config(camera, config, context)
169
+
170
+ context, config = __adapt_camera_settings(camera, camera_settings)
171
+
172
+ # # Push the button
173
+ # remote_release = gp.check_result(gp.gp_widget_get_child_by_name(config, 'eosremoterelease'))
174
+ # gp.gp_widget_set_value(remote_release, "Press 2")
175
+ # # set config
176
+ # gp.gp_camera_set_config(camera, config, context)
177
+
178
+ # Release the button
179
+ # remote_release = gp.check_result(gp.gp_widget_get_child_by_name(config, 'eosremoterelease'))
180
+ # gp.gp_widget_set_value(remote_release, "Release Full")
181
+ # # set config
182
+ # gp.gp_camera_set_config(camera, config, context)
183
+
184
+ # Set mirror lock back to off
185
+ lock = gp.check_result(gp.gp_widget_get_child_by_name(config, 'mirrorlock'))
186
+ gp.gp_widget_set_value(lock, "0")
187
+ # set config
188
+ gp.gp_camera_set_config(camera, config, context)
189
+
190
+
191
+ def get_cameras() -> list:
192
+ """ Returns a list with the cameras.
193
+
194
+ Returns: List with all the attached cameras ([name, USB port]).
195
+ """
196
+
197
+ locale.setlocale(locale.LC_ALL, '')
198
+
199
+ gp.check_result(gp.use_python_logging())
200
+ # make a list of all available cameras
201
+ return list(gp.Camera.autodetect())
202
+
203
+
204
+ def __get_address(camera_name: str) -> str:
205
+ """ Gets the address of the camera if the name is given
206
+
207
+ Args:
208
+ - camera_name: Name of the camera
209
+
210
+ Returns: Address of the camera
211
+ """
212
+
213
+ camera_tuple = [camera[1] for camera in get_cameras() if camera[0] == camera_name]
214
+ try:
215
+ return camera_tuple[0]
216
+ except IndexError:
217
+ raise CameraError(f"Camera {camera_name} not found")
218
+
219
+
220
+ def get_camera(camera_name: str):
221
+ """ Returns the initialized camera object of the selected camera
222
+
223
+ Args:
224
+ - camera_name: Name of the camera
225
+
226
+ Returns: Initialized camera object of the selected camera.
227
+ """
228
+
229
+ addr = __get_address(camera_name)
230
+ if addr == '':
231
+ return ''
232
+
233
+ # get port info
234
+ port_info_list = gp.PortInfoList()
235
+ port_info_list.load()
236
+ abilities_list = gp.CameraAbilitiesList()
237
+ abilities_list.load()
238
+
239
+ camera = gp.Camera()
240
+ idx = port_info_list.lookup_path(addr)
241
+ camera.set_port_info(port_info_list[idx])
242
+ idx = abilities_list.lookup_model(camera_name)
243
+ camera.set_abilities(abilities_list[idx])
244
+
245
+ context = gp.gp_context_new()
246
+
247
+ # Initialize the camera
248
+ try:
249
+ camera.init(context)
250
+
251
+ # find the capture target config item (to save to the memory card)
252
+ config = gp.check_result(gp.gp_camera_get_config(camera, context))
253
+ capture_target = gp.check_result(gp.gp_widget_get_child_by_name(config, 'capturetarget'))
254
+ # set value
255
+ value = gp.check_result(gp.gp_widget_get_choice(capture_target, 1))
256
+ gp.gp_widget_set_value(capture_target, value)
257
+ # set config
258
+ gp.gp_camera_set_config(camera, config, context)
259
+
260
+ # find the drivemode and set to Continuous high speed
261
+ drive_mode = gp.check_result(gp.gp_widget_get_child_by_name(config, 'drivemode'))
262
+ gp.gp_widget_set_value(drive_mode, "Continuous high speed")
263
+ # set config
264
+ gp.gp_camera_set_config(camera, config, context)
265
+ except gphoto2.GPhoto2Error:
266
+ pass
267
+
268
+ return camera
269
+
270
+
271
+ def get_free_space(camera: Camera) -> float:
272
+ """ Return the free space on the card of the selected camera
273
+
274
+ Args:
275
+ - camera: Camera object
276
+
277
+ Returns: Free space on the card of the camera [GB]
278
+ """
279
+ return round(camera.get_storageinfo()[0].freekbytes / 1024 / 1024, 1)
280
+
281
+
282
+ def get_space(camera: Camera) -> float:
283
+ """ Return the size of the memory card of the selected camera
284
+
285
+ Args:
286
+ - camera: Camera object
287
+
288
+ Returns: Size of memory card of the camera [GB]
289
+ """
290
+
291
+ return round(camera.get_storageinfo()[0].capacitykbytes / 1024 / 1024, 1)
292
+
293
+
294
+ def get_shooting_mode(camera_name: str, camera: Camera) -> str:
295
+ """ Return the shooting mode of the selected camera. Should be "Manual".
296
+
297
+ Args:
298
+ - camera: Camera object
299
+
300
+ Returns: Shooting mode of the camera
301
+ """
302
+ if "Canon" in camera_name:
303
+ return camera.get_config().get_child_by_name('autoexposuremodedial').get_value()
304
+ elif "Nikon" in camera_name:
305
+ mode = camera.get_config().get_child_by_name('expprogram').get_value()
306
+ if mode == "M":
307
+ return "Manual"
308
+ else:
309
+ return mode
310
+ else:
311
+ return ""
312
+
313
+
314
+ def get_focus_mode(camera: Camera) -> str:
315
+ """ Return the focus mode of the selected camera. Should be "Manual"
316
+
317
+ Args:
318
+ - camera: Camera object
319
+
320
+ Returns: Focus mode of the camera
321
+ """
322
+
323
+ return camera.get_config().get_child_by_name('focusmode').get_value()
324
+
325
+
326
+ def get_battery_level(camera: Camera) -> str:
327
+ """ Return the battery level of the selected camera
328
+
329
+ Args:
330
+ - camera: Name of the camera
331
+
332
+ Returns: Current battery level of the camera [%]
333
+ """
334
+
335
+ return camera.get_config().get_child_by_name('batterylevel').get_value()
336
+
337
+
338
+ def get_time(camera: Camera) -> str:
339
+ """ Returns the current time of the selected camera
340
+
341
+ Args:
342
+ - camera: Camera object
343
+
344
+ Returns: Current time of the camera
345
+ """
346
+
347
+ # get configuration tree
348
+ config = camera.get_config()
349
+ # find the date/time setting config item and get it
350
+ # name varies with camera driver
351
+ # Canon EOS - 'datetime'
352
+ # PTP - 'd034'
353
+ for name, fmt in (('datetime', '%Y-%m-%d %H:%M:%S'),
354
+ ('d034', None)):
355
+ now = datetime.now()
356
+ ok, datetime_config = gp.gp_widget_get_child_by_name(config, name)
357
+ if ok >= gp.GP_OK:
358
+ widget_type = datetime_config.get_type()
359
+ raw_value = datetime_config.get_value()
360
+ if widget_type == gp.GP_WIDGET_DATE:
361
+ camera_time = datetime.fromtimestamp(raw_value)
362
+ else:
363
+ if fmt:
364
+ camera_time = datetime.strptime(raw_value, fmt)
365
+ else:
366
+ camera_time = datetime.utcfromtimestamp(float(raw_value))
367
+ logging.info('Camera clock: ', camera_time.isoformat(' '))
368
+ logging.info('Computer clock:', now.isoformat(' '))
369
+ err = now - camera_time
370
+ if err.days < 0:
371
+ err = -err
372
+ lead_lag = 'ahead'
373
+ logging.info('Camera clock is ahead by', )
374
+ else:
375
+ lead_lag = 'behind'
376
+ logging.warning('Camera clock is %s by %d days and %d seconds' % (
377
+ lead_lag, err.days, err.seconds))
378
+ break
379
+ else:
380
+ logging.warning('Unknown date/time config item')
381
+ return "Unknown date/time config item"
382
+
383
+ return camera_time.isoformat(' ')
384
+
385
+
386
+ def set_time(camera: Camera) -> None:
387
+ """ Set the computer time on the selected camera """
388
+
389
+ # get configuration tree
390
+ config = camera.get_config()
391
+
392
+ if __set_datetime(config):
393
+ # apply the changed config
394
+ camera.set_config(config)
395
+ else:
396
+ logging.error('Could not set date & time')
397
+
398
+
399
+ def __set_datetime(config) -> bool:
400
+ """ Private method to set the date and time of the camera. """
401
+
402
+ ok, date_config = gp.gp_widget_get_child_by_name(config, 'datetimeutc')
403
+ if ok == -2:
404
+ ok, date_config = gp.gp_widget_get_child_by_name(config, 'datetime')
405
+
406
+ if ok >= gp.GP_OK:
407
+ widget_type = date_config.get_type()
408
+ if widget_type == gp.GP_WIDGET_DATE:
409
+ now = int(time.time())
410
+ date_config.set_value(now)
411
+ else:
412
+ now = time.strftime('%Y-%m-%d %H:%M:%S')
413
+ date_config.set_value(now)
414
+ return True
415
+ return False
416
+
417
+
418
+ def get_camera_dict() -> dict:
419
+ """ Get a dictionary of camera names and their GPhoto2 camera object
420
+ Returns: Dictionary of camera names and their GPhoto2 camera object
421
+ """
422
+ camera_names = get_cameras()
423
+ cameras = dict()
424
+ for camera_name in camera_names:
425
+ cameras[camera_name[0]] = get_camera(camera_name[0])
426
+ return cameras
427
+
428
+
429
+ def get_camera_overview() -> dict:
430
+ """ Returns a dictionary with information of the connected cameras.
431
+
432
+ The keys in the dictionary are the camera names and the values (the camera information) contains information about
433
+ the battery level and space on the memory card of the camera.
434
+
435
+ Returns: Dictionary with information of the connected cameras.
436
+ """
437
+
438
+ camera_overview = {}
439
+
440
+ camera_names = get_cameras()
441
+ for camera_name in camera_names:
442
+ camera = get_camera(camera_name[0])
443
+
444
+ try:
445
+ battery_level = get_battery_level(camera)
446
+ free_space = get_free_space(camera)
447
+ total_space = get_space(camera)
448
+
449
+ camera_overview[camera_name[0]] = CameraInfo(camera_name, battery_level, free_space, total_space)
450
+ # camera.exit()
451
+ except gp.GPhoto2Error:
452
+ logging.error("Could not connect to the camera. Did you start Solar Eclipse Workbench in sudo mode?")
453
+
454
+ return camera_overview
455
+
456
+
457
+ class CameraInfo:
458
+
459
+ def __init__(self, camera_name: str, battery_level: str, free_space: float, total_space: float) -> None:
460
+ """ Create a new CameraInfo object.
461
+
462
+ Args:
463
+ - camera_name: Name of the camera
464
+ - battery_level: Battery level [%]
465
+ - free_space: Free space on the camera memory card [GB]
466
+ - total_space: Total space on the camera memory card [GB]
467
+ """
468
+
469
+ self.camera_name = camera_name
470
+ self.battery_level = battery_level
471
+ self.free_space = free_space
472
+ self.total_space = total_space
473
+
474
+ def get_camera_name(self) -> str:
475
+ """ Returns the name of the camera.
476
+
477
+ Returns: Name of the camera.
478
+ """
479
+ return self.camera_name[0]
480
+
481
+ def get_battery_level(self) -> str:
482
+ """ Returns the battery level of the camera.
483
+
484
+ Returns: Battery level of the camera [%].
485
+ """
486
+ return self.battery_level
487
+
488
+ def get_absolute_free_space(self) -> float:
489
+ """ Returns the absolute free space on the memory card of the camera.
490
+
491
+ Returns: Free space on the memory card of the camera [GB].
492
+ """
493
+
494
+ return self.free_space
495
+
496
+ def get_relative_free_space(self) -> float:
497
+ """ Returns the relative free space on the memory card of the camera.
498
+
499
+ Returns: Free space on the memory card of the camera [%].
500
+ """
501
+
502
+ return self.get_absolute_free_space() / self.get_total_space() * 100
503
+
504
+ def get_total_space(self) -> float:
505
+ """ Returns the total space on the memory card of the camera.
506
+
507
+ Returns: Total space on the memory card of the camera [GB].
508
+ """
509
+ return self.total_space
510
+
511
+
512
+ def main():
513
+ # Get cameras
514
+ cameras = get_cameras()
515
+
516
+ # Get all information for the gui
517
+ get_camera_overview()
518
+
519
+ # Get battery and free space
520
+ for camera in cameras:
521
+ try:
522
+ camera_object = get_camera(camera[0])
523
+
524
+ # Get general info
525
+ print(f"{camera[0]}: {get_battery_level(camera_object)} - {get_free_space(camera_object)} GB "
526
+ f"of {get_space(camera_object)} GB free.")
527
+
528
+ # Check if the lens and the camera are set to manual
529
+ if get_shooting_mode(camera[0], camera_object) != "Manual":
530
+ print("Set the camera in Manual mode!")
531
+ exit()
532
+
533
+ if get_focus_mode(camera_object) != "Manual":
534
+ print("Set the lens in Manual mode!")
535
+ exit()
536
+
537
+ # Set the correct time
538
+ print(get_time(camera_object))
539
+ set_time(camera_object)
540
+
541
+ # Take picture
542
+ camera_settings = CameraSettings(camera[0], "1/1000", "8", 100)
543
+
544
+ take_picture(camera_object, camera_settings)
545
+
546
+ time.sleep(1)
547
+ camera_settings = CameraSettings(camera[0], "1/200", "6.3", 400)
548
+ # take_bracket(camera_object, camera_settings, "+/- 1 2/3")
549
+ take_picture(camera_object, camera_settings)
550
+
551
+ # Mirror lock
552
+ # mirror_lock(camera_object, camera_settings)
553
+
554
+ # take_picture(camera_object, camera_settings)
555
+
556
+ time.sleep(1)
557
+ camera_settings = CameraSettings(camera[0], "1/4000", "5.6", 200)
558
+ take_burst(camera_object, camera_settings, 1)
559
+ time.sleep(3)
560
+ camera_object.exit()
561
+
562
+ except gphoto2.GPhoto2Error:
563
+ print("Could not connect to the camera. Did you start Solar Eclipse Workbench in sudo mode?")
564
+
565
+
566
+ if __name__ == "__main__":
567
+ main()
@@ -0,0 +1,23 @@
1
+ import logging
2
+ import subprocess
3
+
4
+ def execute_command(command: str) -> None:
5
+ """ Executes a command.
6
+
7
+ Args:
8
+ - command: Command to execute, as a string. The command should be a valid bash command.
9
+ """
10
+
11
+ # Execute a bash command in the shell
12
+ logging.info(f"Executing command: {command}")
13
+
14
+ try:
15
+ subprocess.run(command, shell=True, check=True)
16
+ except subprocess.CalledProcessError as e:
17
+ logging.error(f"An error occurred while executing the command: {e}")
18
+ raise
19
+ except Exception as e:
20
+ logging.error(f"An unexpected error occurred: {e}")
21
+ raise
22
+
23
+
Binary file
@@ -0,0 +1,21 @@
1
+ # Add the GPS, check the port,
2
+ # GPSD_SOCKET="/usr/local/var/gpsd.sock" /usr/local/Cellar/gpsd/3.25/sbin/gpsdctl add /dev/ttys010
3
+ from gpsdclient import GPSDClient
4
+
5
+ # get your data as json strings:
6
+ # with GPSDClient(host="127.0.0.1") as client:
7
+ # for result in client.json_stream():
8
+ # print(result)
9
+
10
+ # or as python dicts (optionally convert time information to `datetime` objects)
11
+ with GPSDClient(host="127.0.0.1") as client:
12
+ # for result in client.json_stream():
13
+ # print(result)
14
+ for result in client.dict_stream(convert_datetime=True, filter=["TPV"]):
15
+ print("Latitude: %s" % result.get("lat", "n/a"))
16
+ print("Longitude: %s" % result.get("lon", "n/a"))
17
+
18
+ # you can optionally filter by report class
19
+ with GPSDClient() as client:
20
+ for result in client.dict_stream(filter=["TPV", "SKY"]):
21
+ print(result)
@@ -0,0 +1,27 @@
1
+ import time, pynmea2
2
+
3
+ import serial
4
+
5
+ port = '/dev/serial0'
6
+ port = '/dev/tty'
7
+ baud = 9600
8
+
9
+ serialPort = serial.Serial(port, timeout=0.5)
10
+ while True:
11
+
12
+ str = ''
13
+ try:
14
+ str = serialPort.readline().decode().strip()
15
+ print (str)
16
+ except Exception as e:
17
+ print(e)
18
+ # print(str)
19
+
20
+ if str.find('GGA') > 0:
21
+ try:
22
+ msg = pynmea2.parse(str)
23
+ print(msg.timestamp, 'Lat:', round(msg.latitude, 6), 'Lon:', round(msg.longitude, 6), 'Alt:', msg.altitude,
24
+ 'Sats:', msg.num_sats)
25
+ except Exception as e:
26
+ print(e)
27
+ time.sleep(0.1)