Skip to content

GUI Module

In this module includes all classes that handle and controll the different plots and the GUI. The most important class is the HeliostatUI class. This is the main class of the controll software and connects the frontend to the backend. This class handels the main window from which all functions and plots are controlled. Beside the initialization of the buttons and the variables it also has the function update state which can be seen as the main function.

This function is called every second and first of all asks for the current position of the mirrors and then switches the motors on depending on the current mode that the operator wants to execute. It calls the corresponding functions which in the end all calculate the speed for the two motors. Afterwards this data is sent to the motor and written into a log file via the Logger.log() function from the Logger class. Furthermore it updates the various plots which will be explained later. To prevent the motor from running into a position that could damage the cables or mirrors a limit check is done in the end. In case the motor reaches one of these positions it is turned of immediately.

In the following parts the different functions from the Heliostat are explained. To get further information visit the code.

Class documentation

Bases: QMainWindow

This class represents the main user interface for controlling a heliostat system. It provides functionalities for controlling the heliostat's motion, tracking celestial objects, displaying real-time data plots, and accessing diagnostic information.

Attributes:

Name Type Description
latitude float

The latitude of the heliostat's location.

longitude float

The longitude of the heliostat's location.

altitude float

The altitude of the heliostat's location.

target_ra float

The right ascension (RA) of the target celestial object.

target_dec float

The declination (DEC) of the target celestial object.

trafoA float

Transformation factor for azimuth axis.

trafoE float

Transformation factor for elevation axis.

home_pos dict

Dictionary containing the home position angles for both axes.

passive_speed dict

Dictionary containing the passive tracking speeds for both axes.

M dict

Dictionary containing the motion parameters for both axes.

low_limit_A float

Lower limit for azimuth axis.

high_limit_A float

Upper limit for azimuth axis.

low_limit_E float

Lower limit for elevation axis.

high_limit_E float

Upper limit for elevation axis.

target_offset dict

Dictionary containing the offset for the target position for both axes.

cycle_time int

Time interval for updating the state in milliseconds.

M_max int

Maximum speed for motion parameters.

M_slow_fixed_target float

Speed for fixed target tracking.

M_slow_rel float

Relative speed for passive tracking.

M_slow_rel_Az float

Relative speed for passive tracking in azimuth axis.

M_fine_rel dict

Dictionary containing relative speed for fine tracking for both axes.

M_fine_I dict

Dictionary containing I value for passive tracking for both axes.

M_fine_active dict

Dictionary containing active fine tracking speeds for both axes.

M_fine_active_I dict

Dictionary containing I value for active tracking for both axes.

active_frame int

Maximum allowed error for active tracking.

update_period int

Time interval for updating plots in seconds.

t0 float

Initial time for calculating time component.

timer_count int

Counter for timer ticks.

t_vector list

List containing time values for plotting.

grad2step float

Conversion factor from degrees to motor steps.

E dict

Dictionary containing error values for both axes.

diagnose Diagnose

Instance of Diagnose class for diagnostics plotting.

motion_params MotionParams

Instance of MotionParams class for motion parameters plotting.

q_params FourQ

Instance of FourQ class for 4Q sensor data plotting.

spatial_view SpatialView

Instance of SpatialView class for spatial view plotting.

real_time_data_window RealTimeData

Instance of RealTimeData class for real-time data plotting.

passivPI_E PI

Instance of PI class for passive tracking in elevation axis.

passivPI_A PI

Instance of PI class for passive tracking in azimuth axis.

activePI_E PI

Instance of PI class for active tracking in elevation axis.

activePI_A PI

Instance of PI class for active tracking in azimuth axis.

fourq Q4

Instance of Q4 class for handling 4Q sensor data.

io IO

Instance of IO class for input/output operations.

current_date str

Current date in "YYYY-MM-DD" format.

log_file_path str

Path for log file.

logger WorkerLogger

Instance of WorkerLogger class for logging data.

thread_receiving QThread

Thread for receiving data from CAN Bus.

worker_receiving WorkerReceiving

Worker for receiving data from CAN Bus.

timer QTimer

Timer for updating state.

timer_fourq QTimer

Timer for updating 4Q data.

Source code in GUI.py
 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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
class HeliostatUI(QMainWindow):
    """
    This class represents the main user interface for controlling a heliostat system.
    It provides functionalities for controlling the heliostat's motion, tracking celestial objects,
    displaying real-time data plots, and accessing diagnostic information.

    Attributes:
        latitude (float): The latitude of the heliostat's location.
        longitude (float): The longitude of the heliostat's location.
        altitude (float): The altitude of the heliostat's location.
        target_ra (float): The right ascension (RA) of the target celestial object.
        target_dec (float): The declination (DEC) of the target celestial object.
        trafoA (float): Transformation factor for azimuth axis.
        trafoE (float): Transformation factor for elevation axis.
        home_pos (dict): Dictionary containing the home position angles for both axes.
        passive_speed (dict): Dictionary containing the passive tracking speeds for both axes.
        M (dict): Dictionary containing the motion parameters for both axes.
        low_limit_A (float): Lower limit for azimuth axis.
        high_limit_A (float): Upper limit for azimuth axis.
        low_limit_E (float): Lower limit for elevation axis.
        high_limit_E (float): Upper limit for elevation axis.
        target_offset (dict): Dictionary containing the offset for the target position for both axes.
        cycle_time (int): Time interval for updating the state in milliseconds.
        M_max (int): Maximum speed for motion parameters.
        M_slow_fixed_target (float): Speed for fixed target tracking.
        M_slow_rel (float): Relative speed for passive tracking.
        M_slow_rel_Az (float): Relative speed for passive tracking in azimuth axis.
        M_fine_rel (dict): Dictionary containing relative speed for fine tracking for both axes.
        M_fine_I (dict): Dictionary containing I value for passive tracking for both axes.
        M_fine_active (dict): Dictionary containing active fine tracking speeds for both axes.
        M_fine_active_I (dict): Dictionary containing I value for active tracking for both axes.
        active_frame (int): Maximum allowed error for active tracking.
        update_period (int): Time interval for updating plots in seconds.
        t0 (float): Initial time for calculating time component.
        timer_count (int): Counter for timer ticks.
        t_vector (list): List containing time values for plotting.
        grad2step (float): Conversion factor from degrees to motor steps.
        E (dict): Dictionary containing error values for both axes.
        diagnose (Diagnose): Instance of Diagnose class for diagnostics plotting.
        motion_params (MotionParams): Instance of MotionParams class for motion parameters plotting.
        q_params (FourQ): Instance of FourQ class for 4Q sensor data plotting.
        spatial_view (SpatialView): Instance of SpatialView class for spatial view plotting.
        real_time_data_window (RealTimeData): Instance of RealTimeData class for real-time data plotting.
        passivPI_E (PI): Instance of PI class for passive tracking in elevation axis.
        passivPI_A (PI): Instance of PI class for passive tracking in azimuth axis.
        activePI_E (PI): Instance of PI class for active tracking in elevation axis.
        activePI_A (PI): Instance of PI class for active tracking in azimuth axis.
        fourq (Q4): Instance of Q4 class for handling 4Q sensor data.
        io (IO): Instance of IO class for input/output operations.
        current_date (str): Current date in "YYYY-MM-DD" format.
        log_file_path (str): Path for log file.
        logger (WorkerLogger): Instance of WorkerLogger class for logging data.
        thread_receiving (QThread): Thread for receiving data from CAN Bus.
        worker_receiving (WorkerReceiving): Worker for receiving data from CAN Bus.
        timer (QTimer): Timer for updating state.
        timer_fourq (QTimer): Timer for updating 4Q data.
    """

    def __init__(self):
        """

        Initializes the HeliostatUI instance and the main window

        """

        # Create Window
        super(HeliostatUI, self).__init__()
        loadUi(resource_path("ui_files/Heliostat.ui"), self)
        self.setWindowTitle("GUI Heliostat")


        self.state = 0
        self.latitude = 46.81
        self.longitude = 9.84
        self.altitude = 1580
        self.target_ra = 0
        self.target_dec = 0

        self.trafoA = -5
        self.trafoE = 27

        self.pos = {'A': 180, 'E': 63, 'FQ': [0,0]}

        self.home_pos = {'E': 63, 'A': 180}

        self.passive_speed = {'E': 0 , 'A': 0}
        self.M = {'E': 0, 'A': 0}
        self.low_limit_A = 83
        self.high_limit_A = 218
        self.low_limit_E = 30
        self.high_limit_E = 91

        self.target_offset = {'A': 0, 'E': 0}
        self.target = {'A': 180, 'E':63}
        self.cycle_time = 1000

        self.M_max=5000;  # sps
        self.M_slow_fixed_target=0.1
        self.M_slow_rel=0.07;
        self.M_slow_rel_Az=0.1;

        self.M_fine_rel = {'A':-0.0040000000000000036 , 'E': 0}
        self.M_fine_I = {'A': 0 , 'E': 0}

        #self.M_fine_relA=-0.1%-0.008; # scaler for fine tracking
        #self.M_fine_relE=-0.01%-0.01; # scaler for fine tracking
        #self.M_fine_I_E= -0.000; # I value for passive tracking 
        #self.M_fine_I_A= -0.000; # I value for passive tracking

        self.M_fine_active = {'A': 0.04 , 'E': 0.02}
        self.M_fine_active_I = {'A': 0.0044 , 'E': 0.0004}

        #self.M_fine_active_E=0.02 
        #self.M_fine_active_A=0.04
        #self.M_fine_active_I_E=0.0004 
        #self.M_fine_active_I_A=0.0004

        self.active_frame = 3
        self.update_period = 2
        time_component = datetime.now()-datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        self.t0 = time_component.total_seconds()/3600
        self.timer_count = 0
        self.t_vector = []

        self.grad2step=256/3.6*160
        self.E = {'DA':0,'DE':0}

        #Initializing classes for further use
        self.diagnose = Diagnose()
        self.motion_params = MotionParams()
        self.q_params = FourQ()
        self.spatial_view = SpatialView()
        self.real_time_data_window = RealTimeData()
        self.passivPI_E = PI(10, self.M_fine_rel['E'], self.M_fine_I['E'], self.grad2step)
        self.passivPI_A = PI(4, self.M_fine_rel['A'], self.M_fine_I['A'], self.grad2step)
        self.activePI_E = PI(10, self.M_fine_active['E'], self.M_fine_active_I['E'], self.grad2step)
        self.activePI_A = PI(2, self.M_fine_active['A'], self.M_fine_active_I['A'], self.grad2step)

        self.fourq = Q4()
        self.io = IO(self.fourq)         
        self.current_date = datetime.now().strftime("%Y-%m-%d")
        self.log_file_path = f"log/heliostat_log_type2_{self.current_date}.dat"
        self.logger = WorkerLogger(self.log_file_path)


        #Initializing Thread to receive data from CAN Bus
        self.thread_receiving = QThread()
        self.worker_receiving = WorkerReceiving(self.io)
        self.worker_receiving.moveToThread(self.thread_receiving)
        self.thread_receiving.start()
        self.thread_receiving.started.connect(self.worker_receiving.run)

        #Initializing timer to update state
        self.timer = QTimer()
        self.timer.setInterval(self.cycle_time)
        self.timer.timeout.connect(self.update_state)
        self.timer.start()


        #Initializing timer to get 4Q data
        self.timer_fourq= QTimer()
        self.timer_fourq.setInterval(self.cycle_time)
        self.timer_fourq.timeout.connect(lambda: self.fourq.go())
        self.timer_fourq.start()





        # Connect GUI widgets to backend 
        self.stop_button.clicked.connect(self.toggle_stop)
        self.home_button.clicked.connect(self.toggle_home)
        self.passiv_tracking_button.clicked.connect(self.toggle_passive_tracking)
        self.active_tracking_button.clicked.connect(self.toggle_active_tracking)
        self.go_to_button.clicked.connect(self.toggle_go_to)
        self.passiv_star_tracking_button.clicked.connect(self.toggle_passive_star_tracking)
        self.manual_remote_button.clicked.connect(self.toggle_manual_remote)
        self.camera_button.clicked.connect(self.toggle_camera)


        self.real_time_data_button.clicked.connect(self.toggle_real_time_data_window)
        self.diagnose_button.clicked.connect(self.toggle_diagnose)
        self.motion_params_button.clicked.connect(self.toggle_motion_params)
        self.q_params_button.clicked.connect(self.toggle_q_params)
        self.view_button.clicked.connect(self.toggle_view)

        self.input_pos_a.valueChanged.connect(self.set_target_pos_a)
        self.input_pos_e.valueChanged.connect(self.set_target_pos_e)
        self.input_active_frame.valueChanged.connect(self.set_active_frame)
        self.input_right_ascension.valueChanged.connect(self.set_ra)
        self.input_declination.valueChanged.connect(self.set_dec)
        self.input_speed.valueChanged.connect(self.set_speed)


        self.set_target_pos_a(float(self.input_pos_a.text().replace(",", ".")))
        self.set_target_pos_e(float(self.input_pos_e.text().replace(",", ".")))
        self.set_active_frame(float(self.input_active_frame.text().replace(",", ".")))
        self.set_ra(float(self.input_right_ascension.text().replace(",", ".")))
        self.set_dec(float(self.input_declination.text().replace(",", ".")))
        self.set_speed(float(self.input_speed.text().replace(",", ".")))

        self.stop_button.setEnabled(True)
        self.home_button.setEnabled(True)
        self.passiv_tracking_button.setEnabled(True)
        self.active_tracking_button.setEnabled(True)
        self.go_to_button.setEnabled(True)
        self.passiv_star_tracking_button.setEnabled(True)
        self.manual_remote_button.setEnabled(True)
        self.camera_button.setEnabled(True)

        self.real_time_data_button.setEnabled(True)
        self.diagnose_button.setEnabled(True)
        self.motion_params_button.setEnabled(True)
        self.q_params_button.setEnabled(True)
        self.view_button.setEnabled(True)

    def toggle_real_time_data_window(self):
        """
        Toggles for the real time data plot.

        Returns:
            None
        """
        if self.real_time_data_window.isVisible():
            self.real_time_data_window.close()
        else:
            self.real_time_data_window.show()

    def toggle_diagnose(self):
        """
        Toggles for the diagnose data plot.

        Returns:
            None
        """
        if self.diagnose.isVisible():
            self.diagnose.close()
        else:
            self.diagnose.show()

    def toggle_motion_params(self):
        """
        Toggle for the motion parameters plot.

        Returns:
            None
        """
        if self.motion_params.isVisible():
            self.motion_params.close()
        else:
            self.motion_params.show()
    def toggle_q_params(self):
        """
        Toggle for the 4Q sensor data plot.

        Returns:
            None
        """
        if self.q_params.isVisible():
            self.q_params.close()
        else:
            self.q_params.show()

    def toggle_view(self):
        """
        Toggle for the 3D/2D view.

        Returns:
            None
        """
        if self.spatial_view.isVisible():
            self.spatial_view.close()
        else:
            self.spatial_view.show()


    def set_target_pos_a(self, value):
        """
        Set the target position for the primary axis

        Args:
            value (float): The position to set for the primary axis

        Returns:
            None
        """
        self.target['A'] = value

    def set_target_pos_e(self, value):
        """
        Set the target position for the secondary axis

        Args:
            value (float): The position to set for the second axis

        Returns:
            None
        """
        self.target['E'] = value

    def set_active_frame(self, value):
        """
        Set the maximum aloud error for active tracking

        Args:
            value (float): The maximum error to set for active tracking

        Returns:
            None
        """
        self.active_frame = value

    def set_ra(self, value):
        """
        Set the right ascension (RA) target value.

        Args:
            value (float): The value to set as the target RA.

        Returns:
            None
        """
        self.target_ra = value

    def set_dec(self, value):
        """
        Set the declination (DEC) target value.

        Args:
            value (float): The value to set as the target DEC.

        Returns:
            None
        """
        self.target_dec = value

    def set_speed(self, value):
        """
        Set the speed for manual remote controll

        Args:
            value (float): The speed to set for the manual remote mode

        Returns:
            None
        """
        self.speed = value




    def toggle_stop(self):
        """
        Toggle to change the current mode to stop

        Returns:
            None
        """
        self.mode.setText("Stop")
        self.state = 0
    def toggle_home(self):
        """
        Toggle to change the current mode to home

        Returns:
            None
        """
        self.mode.setText("Home")
        self.state = 1
    def toggle_go_to(self):
        """
        Toggle to change the current mode to going to a certain position

        Returns:
            None
        """
        self.mode.setText("Fixed position")
        self.state = 2
    def toggle_passive_tracking(self):
        """
        Toggle to change the current mode to passive tracking

        Returns:
            None
        """
        self.mode.setText("Passive tracking")
        self.state = 3
    def toggle_active_tracking(self):
        """
        Toggle to change the current mode to active tracking

        Returns:
            None
        """
        self.mode.setText("Active tracking")
        self.state = 4
    def toggle_passive_star_tracking(self):
        """
        Toggle to change the current mode to star tracking

        Returns:
            None
        """
        self.mode.setText("Passive star tracking")
        self.state = 5
    def toggle_manual_remote(self):
        """
        Toggle to change the current mode to manual remote

        Returns:
            None
        """
        self.mode.setText("Manual remote")
        self.state = 6

    def toggle_camera(self):
        """
        Toggle to open the site with access to the camera that is installed remotly. 

        Returns:
            None
        """
        url = 'http://srv-wrc-syno2.ad.pmodwrc.ch:5000/index.cgi?launchApp=SYNO.SDS.SurveillanceStation#/signin'
        webbrowser.open(url)



    def update_state(self):
        """
        This function updates the state of the system according to the current state value.
        It performs various actions based on the state, such as stopping motion, going to the home position,
        going to a fixed target, passive sun tracking, active sun tracking, star tracking, manual remote control,
        and logging data. It also updates real-time data plots, sends speed data to the motor
        and performs limit checks to prevent reaching limit positions which could damage the mirror or cables.

        Args:
            None

        Returns:
            None

        """
        self.timer_count += 1
        self.pos = self.io.getdata()

        if self.state == 0:
            self.stop()
            self.real_time_data_window.update_mode("Stop") 
        elif self.state == 1:
            self.home(self.pos)
        elif self.state == 2:
            self.fixed_target() 
        elif self.state == 3:
            self.passive_tracking(self.pos)
        elif self.state == 4:
            self.active_tracking(self.pos)

        elif self.state == 5:
            self.star_tracking(self.pos)
            print("star tracking")
        elif self.state == 6:
            self.manual_control()
        elif self.state == 7:
            self.stop() 
        self.real_time_data_window.update_variables(self.M,self.pos)
        self.t = self.timer_count*self.update_period/3600+self.t0
        self.update_plots()
        self.soft_end_switch = self.io.send_data(self.M)
        self.io.request_motor_data()
        self.logger.log(self.t,self.state,self.pos,self.M,self.E)


        if self.soft_end_switch == 1:
            self.state = 0
            self.real_time_data_window.update_mode("Reached soft end switch") 



        #Making a limit check to prevent from limit positions 
        if self.pos['A'] < self.low_limit_A and self.M['A'] < 0:
            self.state = 7
        elif self.pos['A'] > self.high_limit_A and self.M['A'] > 0:
            self.state = 7 

        if self.pos['E'] < self.low_limit_E and self.M['E'] < 0:
            self.state = 7
        elif self.pos['E'] > self.high_limit_E and self.M['E'] > 0:
            self.state = 7 

    def update_plots(self):
        """
        This function updates the plots displayed in different widgets with new data.
        It appends the current time value to the time vector and updates the diagnose plot,
        motion parameters plot, 4Q plot, and spatial view plot with the updated data.

        Args:
            None

        Returns:
            None
        """
        self.t_vector.append(self.t)
        if len(self.t_vector) == 100:
            self.t_vector = self.t_vector[1:]
        self.diagnose.update_diagnose_plot(self.t_vector,self.M)
        self.motion_params.update_motion_plot(self.t_vector, self.pos, self.E)  
        self.q_params.update_fourq_plot(self.t_vector,self.pos)
        self.spatial_view.update_view_plot(self.pos)     

    def home(self, pos):
        """
        This function checks if the system is at the home position by comparing the current
        position with the predefined home position with a tolerance of +/- 0.1 degrees for
        both primary and secondary axes. If the system is at the home position, it sets the
        state to 0 (stopped) and updates the real-time data window to indicate that the
        home position is reached and the motors are off. If the system is not at the home
        position, it initiates movement towards the home position using the 'go_to_target'
        function and updates the real-time data window to indicate that the system is
        moving towards the home position.

        Args:
            pos (dict): Current position of the system.

        Returns:
            None
        """

        if self.home_pos['E']-0.1 < pos['E'] < self.home_pos['E']+ 0.1 and self.home_pos['A'] - 0.1 < pos['A'] < self.home_pos['A'] + 0.1:
            self.state = 0
            self.real_time_data_window.update_mode("Home position reached, motors off")
        else:
            self.go_to_target(pos, self.home_pos['A'],self.home_pos['E'])
            self.real_time_data_window.update_mode("Moving towards home position")

    def fixed_target(self):
        """
        This function calculates the motion required to move the system towards a fixed target position.
        It uses the 'go_to_target' function to initiate movement towards the target position based on the
        current position ('pos') and the target position ('target'). It updates the motion parameters in ('M')
        and updates the real-time data window to indicate that the system is moving towards the target position.

        Args:
            None

        Returns:
            dict: The motion parameters required to move towards the fixed target position.
        """
        self.M = self.go_to_target(self.pos, self.target['A'], self.target['E'])
        mode = f"Moving towards {self.target['A']}/{self.target['E']}"
        self.real_time_data_window.update_mode(mode)

    def go_to_target(self, POS,TA,TE):
        """
        This function calculates the motion parameters required to move the system from its current position ('POS')
        towards a target position specified by the target angles ('TA' for primary axis and 'TE' for secondary axis).
        It computes the differences between the current position and the target position for both axes ('DA' and 'DE').
        Based on these differences, it determines the motion required for each axis ('M['A']' and 'M['E']').
        If the difference in position is significant (> 2 degrees), the maximum speed ('M_max') is used.
        Otherwise, a slower speed ('M_slow_fixed_target') is calculated proportionally to the difference.
        If the difference is very small (< 0.01 degrees), the motion for that axis is set to 0.
        The calculated motion parameters are returned.

        Args:
            POS (dict): Current position of the system
            TA (float): Target angle for the primary axis.
            TE (float): Target angle for the secondary axis.

        Returns:
            dict: The motion parameters required to move towards the target position.
        """
        DA = POS['A'] - TA
        DE = POS['E'] - TE

        if DE < 0 and DE < -2:
            self.M['E'] = -self.M_max
        elif DE > 0 and DE > 2:
            self.M['E'] = self.M_max
        else:
            self.M['E'] = DE * self.M_slow_fixed_target * self.grad2step

        if DA < 0 and DA < -2:
            self.M['A'] = self.M_max
        elif DA > 0 and DA > 2:
            self.M['A'] = -self.M_max
        elif 0 <= DA < 0.01:
            self.M['A'] = 0
        else:
            self.M['A'] = DA * -self.M_slow_fixed_target * self.grad2step
        return self.M

    def passive_tracking(self, pos):
        """  
        This function implements passive tracking, which adjusts the system's motion based on the difference
        between the current position and a reference position (position that directs the beam of the sun into the laboratory). 
        It calculates the differences ('DA' and 'DE') between the current position ('pos') and the reference position adjusted with an offset.
        It then adjusts the system's motion using proportional-integral (PI) controllers for both azimuth and
        elevation axes, taking into account the passive speed settings and the current state of the system.
        The system adjusts its motion differently based on whether the differences are significant or within
        fine-tuning thresholds. It updates the system's state and motion parameters accordingly and updates
        the real-time data window to indicate the tracking mode.

        Args:
            pos (dict): Current position of the system.

        Returns:
            None
        """
        self.pos_and_speed()
        DA = pos['A'] - (self.pos1['A']-self.target_offset['A'])
        DE = pos['E'] - (90+(self.pos1['E']-self.target_offset['E']))/2

        self.passive_speed['A'] = 2*self.passive_speed['A']
        self.passivPI_E.feed(DA, self.passive_speed['A'])
        self.passivPI_A.feed(DE, self.passive_speed['E'])

        #Elevation
        if DE < 0 and DE < -1:
            self.M['E'] = -self.M_max
            self.state_secondary = 1
        elif DE > 0 and DE > 1:
            self.M['E'] = self.M_max
            self.state_secondary = 1
        elif DE > 0.01 or DE < -0.01:
            self.state_secondary = 0
            self.M['E'] = -(self.passive_speed['E']*self.grad2step-DE*self.grad2step*self.M_slow_rel)
        else:
            self.state_secondary = 0
            self.M['E'] = -self.passivPI_E.get_speed()

        #Azimuth   
        if DA < 0 and DA < -1:
            self.state_primary = 1
            self.M['A'] = self.M_max
        elif DA > 0 and DA > 1:
            self.state_primary = 1
            self.M['A'] = -self.M_max 
        elif DA > 0.01 or DA <-0.01:
            self.state_primary = 0
            self.M['A'] = self.passive_speed['A']*self.grad2step-DA*self.grad2step*self.M_slow_rel_Az
        else:
            self.state_primary = 0
            self.M['A'] = self.passivPI_A.get_speed()

        if self.state_secondary == 1 and self.state_primary == 1:
            self.real_time_data_window.update_mode("Passive tracking: coarse")

        elif self.state_secondary == 0 and self.state_primary == 0:
            self.real_time_data_window.update_mode("Passive tracking: fine")


        self.E['DA'] = DA
        self.E['DE'] = DE

    def active_tracking(self, pos):
        """
        This function implements active tracking, which adjusts the system's motion based on feedback from
        the 4Q sensor. It calculates the differences ('DA' and 'DE') between the current position ('pos')
        and a reference position adjusted with an offset. It computes the orientation angles ('DAO' and 'DEO')
        from the 4Q sensor feedback and adjusts the system's motion using proportional-integral (PI)
        controllers for both azimuth and elevation axes, taking into account the active sensor feedback
        and the system's state. The function evaluates whether the system is within the active tracking frame
        and updates the tracking mode accordingly. If the system is out of frame, it switches to passive tracking.
        It then adjusts the system's motion based on the calculated speeds and updates the motion parameters ('M').
        Finally, it updates the error values for logging purposes.

        Args:
            pos (dict): Current position of the system.

        Returns:
            None
        """

        #print(pos['FQ'][0])
        #print(pos['FQ'][1])

        DAO = np.arctan(pos['FQ'][0])/ np.sin(np.deg2rad(90-self.pos['E']))/2
        DEO = np.arctan(pos['FQ'][1])/2

        if DAO == np.nan:
            DAO = 0
        if DEO == np.nan:
            DEO = 0

        self.pos_and_speed()
        DA = pos['A'] - (self.pos1['A']-self.target_offset['A'])
        DE = pos['E'] - (90+(self.pos1['E']-self.target_offset['E']))/2

        self.passive_speed['A'] = 2*self.passive_speed['A']

        self.activePI_A.feed(DAO, self.passive_speed['A'])
        self.activePI_E.feed(DEO, self.passive_speed['E'])

        if abs(DE) < self.active_frame and abs(DA) < self.active_frame:
            self.active_tracking_condition = True
        else:
            self.active_tracking_condition = False


        if self.active_tracking_condition:
            if np.all(self.activePI_E.array == 0):
                self.real_time_data_window.update_mode(f"No active signal, tracking still in frame {self.active_frame} deg")
            else:
                self.real_time_data_window.update_mode(f"Active tracking in frame, frame size {self.active_frame} deg")    
        else:
            self.real_time_data_window.update_mode("Out of frame: switch to passive tracking")
            #self.state = 3 # Fraglich ob überhaupt benötigt wird weil automatischer wechsel zwischen active und passive tracking stattfindet in der active tracking funktion

        if DEO < 0 and DEO < -2:
            self.M['E'] = self.M_max
        elif DEO > 0 and DEO > 2:
            self.M['E'] = -self.M_max
        elif self.active_tracking_condition == False:
            self.M['E'] = -(self.passive_speed['E']*self.grad2step-DE*self.grad2step*self.M_slow_rel)
        else:
            self.M['E'] = -self.activePI_E.get_speed()

        if DAO < 0 and DAO < -2:
            self.M['A'] = -self.M_max
        elif DAO > 0 and DAO > 2:
            self.M['A'] = self.M_max
        elif self.active_tracking_condition == False:
            self.M['A'] = self.passive_speed['A']*self.grad2step-DA*self.grad2step*self.M_slow_rel_Az
        elif abs(DA) > 0.2:
            self.M['A'] = self.passive_speed['A']*self.grad2step+DAO*self.grad2step*self.M_slow_rel_Az
        else:
            self.M['A'] = self.activePI_A.get_speed()

        self.E['DA'] = DA
        self.E['DE'] = DE

    def star_tracking(self, pos):
        """
        This function implements star tracking, which adjusts the system's motion to track a celestial object
        based on its right ascension (RA) and declination (Dec). It calculates the azimuth and zenith
        angles of the star using the target RA and Dec and transforms them into the system's coordinate system.
        The function then calculates the differences ('DA' and 'DE') between the current position ('pos') and
        the calculated position of the star. Based on these differences, it adjusts the system's motion to
        track the star using proportional control. The motion parameters ('M') are updated accordingly.

        Args:
            pos (dict): Current position of the system.

        Returns:
            None
        """
        azimuth, zenith = self.get_angles_star(self.target_ra, self.target_dec)
        star = {'A': azimuth, 'E':90-zenith}
        Heli_star = PRM.polar_rotation(star['A'],90-star['E'], self.trafoA, self.trafoE)
        DA = pos['A'] - Heli_star['A']
        DE = pos['E'] - (90+Heli_star['E'])/2


        #Fraglich ob star tracking so funktioniert oder besser gleiches prinzip wie im passive tracking
        if DE < 0 and DE < -1:
            self.M['E'] = -self.M_max   
        elif DE > 0 and DE > 1:
            self.M['E'] = self.M_max
        else:
            self.M['E'] = DE * -self.M_slow_rel

        if DA < 0 and DA < -1:
            self.M['E'] = -self.M_max   
        elif DA > 0 and DA > 1:
            self.M['E'] = self.M_max
        else:
            self.M['E'] = DA * -self.M_slow_rel


    def manual_control(self):
        """
        This function enables manual control of the system based on user input. It requests manual input
        from the input/output interface and adjusts the system's motion accordingly.
        It checks the status of each motor and adjusts the system's motion speed based on the received commands.
        The motion parameters ('M') are updated accordingly for both azimuth ('A') and elevation ('E') axes.

        Args:
            None

        Returns:
            None
        """
        self.io.request_manual_input()
        #print(self.io.M_rep_M)
        try:
            statusE = bin(int(self.io.M_rep_M['E']))
            if statusE[-1] == "1":
                #print("ror")
                self.M['E'] = -self.speed
            elif statusE[-2] == "1":
                #print("rol")
                self.M['E'] = self.speed
            else:
                #print("stop")
                self.M['E'] = 0
        except:
            print("No valid reply from motor E")
            self.M['E'] = 0

        try:
            statusA = bin(int(self.io.M_rep_M['A']))
            if statusA[-1] == "1":
                #print("ror")
                self.M['A'] = -self.speed
            elif statusA[-2] == "1":
                #print("rol")
                self.M['A'] = self.speed
            else:
                #print("stop")
                self.M['A'] = 0
        except:
            print("No valid reply from motor A")
            self.M['A'] = 0

    def pos_and_speed(self):
        """
        This function calculates the current position and speed of the system based on the current time.
        It obtains the azimuth and zenith angles at two consecutive time points and converts them into
        the system's coordinate system using polar rotation. It then calculates the speed of the system
        by taking the difference in position between the two time points which is further used in passive tracking. 

        Args:
            None

        Returns:
            None
        """

        now = datetime.now()
        azimuth, zenith = self.get_angles(now)
        self.pos1 = PRM.polar_rotation(self, azimuth,90-zenith, self.trafoA, self.trafoE)

        now1 = datetime.now() + timedelta(seconds=1)
        azimuth, zenith = self.get_angles(now1)
        self.pos2 = PRM.polar_rotation(self, azimuth,90-zenith, self.trafoA, self.trafoE)

        self.passive_speed['A'] =  self.pos2['A'] - self.pos1['A']   # speed calculation in grad/second (important for PI Controller afterwards)
        self.passive_speed['E'] =  self.pos2['E'] - self.pos1['E']   # speed calculation in grad/second (important for PI Controller afterwards)

    def get_angles(self, time):
        """
        This function calculates the solar azimuth and zenith angles based on the given time.
        It uses the 'get_solarposition' function to obtain the solar position for the specified time
        and location. The azimuth and zenith angles are extracted from the solar position data.

        Args:
            time (datetime): The time at which the solar angles are to be calculated.

        Returns:
            tuple: A tuple containing the solar azimuth and zenith angles.
        """
        timestamp = pd.Timestamp(time, tz='Europe/Zurich')
        solar_position = get_solarposition(timestamp, self.latitude, self.longitude, self.altitude)
        azimuth = solar_position['azimuth'].values
        zenith = solar_position['apparent_zenith'].values
        return azimuth,zenith

    def get_angles_star(self, target_ra, target_dec):
        """
        This function calculates the azimuth and zenith angles for a celestial object based on
        its right ascension (RA) and declination (Dec). It creates a SkyCoord object for the specified
        RA and Dec, transforms it to an AltAz frame, and obtains the azimuth and altitude angles.
        The calculated azimuth and zenith angles are returned.

        Args:
            target_ra (float): Right ascension of the target celestial object (in degrees).
            target_dec (float): Declination of the target celestial object (in degrees).

        Returns:
            tuple: A tuple containing the azimuth and zenith angles of the celestial object.
        """
        position = SkyCoord(ra=target_ra*u.degree, dec=target_dec*u.degree, frame='icrs')
        location = EarthLocation(lat=self.latitude*u.deg, lon=self.longitude*u.deg, height=self.altitude*u.m)
        time = Time(datetime.now())

        altaz = position.transform_to(AltAz(obstime=time,location=location))
        alt = altaz.alt.deg
        az = altaz.az.deg
        return az, alt

    def stop(self):
        """
        This function stops the motion of the system.

        Args:
            None

        Returns:
            None
        """
        self.M['E'] = 0
        self.M['A'] = 0



    #Things to do when the application gets closed   
    def closeEvent(self,event):
        """
        This function handls the close event of the main window.

        Args:
            event: The close event triggered by closing the main window.

        Returns:
            None
        """
        self.real_time_data_window.close()
        self.motion_params.close()
        self.q_params.close()
        self.diagnose.close()
        self.spatial_view.close()
        self.timer.stop()
        self.timer_fourq.stop()
        self.fourq.delete()
        self.io.delete()
        event.accept()

__init__()

Initializes the HeliostatUI instance and the main window

Source code in GUI.py
def __init__(self):
    """

    Initializes the HeliostatUI instance and the main window

    """

    # Create Window
    super(HeliostatUI, self).__init__()
    loadUi(resource_path("ui_files/Heliostat.ui"), self)
    self.setWindowTitle("GUI Heliostat")


    self.state = 0
    self.latitude = 46.81
    self.longitude = 9.84
    self.altitude = 1580
    self.target_ra = 0
    self.target_dec = 0

    self.trafoA = -5
    self.trafoE = 27

    self.pos = {'A': 180, 'E': 63, 'FQ': [0,0]}

    self.home_pos = {'E': 63, 'A': 180}

    self.passive_speed = {'E': 0 , 'A': 0}
    self.M = {'E': 0, 'A': 0}
    self.low_limit_A = 83
    self.high_limit_A = 218
    self.low_limit_E = 30
    self.high_limit_E = 91

    self.target_offset = {'A': 0, 'E': 0}
    self.target = {'A': 180, 'E':63}
    self.cycle_time = 1000

    self.M_max=5000;  # sps
    self.M_slow_fixed_target=0.1
    self.M_slow_rel=0.07;
    self.M_slow_rel_Az=0.1;

    self.M_fine_rel = {'A':-0.0040000000000000036 , 'E': 0}
    self.M_fine_I = {'A': 0 , 'E': 0}

    #self.M_fine_relA=-0.1%-0.008; # scaler for fine tracking
    #self.M_fine_relE=-0.01%-0.01; # scaler for fine tracking
    #self.M_fine_I_E= -0.000; # I value for passive tracking 
    #self.M_fine_I_A= -0.000; # I value for passive tracking

    self.M_fine_active = {'A': 0.04 , 'E': 0.02}
    self.M_fine_active_I = {'A': 0.0044 , 'E': 0.0004}

    #self.M_fine_active_E=0.02 
    #self.M_fine_active_A=0.04
    #self.M_fine_active_I_E=0.0004 
    #self.M_fine_active_I_A=0.0004

    self.active_frame = 3
    self.update_period = 2
    time_component = datetime.now()-datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    self.t0 = time_component.total_seconds()/3600
    self.timer_count = 0
    self.t_vector = []

    self.grad2step=256/3.6*160
    self.E = {'DA':0,'DE':0}

    #Initializing classes for further use
    self.diagnose = Diagnose()
    self.motion_params = MotionParams()
    self.q_params = FourQ()
    self.spatial_view = SpatialView()
    self.real_time_data_window = RealTimeData()
    self.passivPI_E = PI(10, self.M_fine_rel['E'], self.M_fine_I['E'], self.grad2step)
    self.passivPI_A = PI(4, self.M_fine_rel['A'], self.M_fine_I['A'], self.grad2step)
    self.activePI_E = PI(10, self.M_fine_active['E'], self.M_fine_active_I['E'], self.grad2step)
    self.activePI_A = PI(2, self.M_fine_active['A'], self.M_fine_active_I['A'], self.grad2step)

    self.fourq = Q4()
    self.io = IO(self.fourq)         
    self.current_date = datetime.now().strftime("%Y-%m-%d")
    self.log_file_path = f"log/heliostat_log_type2_{self.current_date}.dat"
    self.logger = WorkerLogger(self.log_file_path)


    #Initializing Thread to receive data from CAN Bus
    self.thread_receiving = QThread()
    self.worker_receiving = WorkerReceiving(self.io)
    self.worker_receiving.moveToThread(self.thread_receiving)
    self.thread_receiving.start()
    self.thread_receiving.started.connect(self.worker_receiving.run)

    #Initializing timer to update state
    self.timer = QTimer()
    self.timer.setInterval(self.cycle_time)
    self.timer.timeout.connect(self.update_state)
    self.timer.start()


    #Initializing timer to get 4Q data
    self.timer_fourq= QTimer()
    self.timer_fourq.setInterval(self.cycle_time)
    self.timer_fourq.timeout.connect(lambda: self.fourq.go())
    self.timer_fourq.start()





    # Connect GUI widgets to backend 
    self.stop_button.clicked.connect(self.toggle_stop)
    self.home_button.clicked.connect(self.toggle_home)
    self.passiv_tracking_button.clicked.connect(self.toggle_passive_tracking)
    self.active_tracking_button.clicked.connect(self.toggle_active_tracking)
    self.go_to_button.clicked.connect(self.toggle_go_to)
    self.passiv_star_tracking_button.clicked.connect(self.toggle_passive_star_tracking)
    self.manual_remote_button.clicked.connect(self.toggle_manual_remote)
    self.camera_button.clicked.connect(self.toggle_camera)


    self.real_time_data_button.clicked.connect(self.toggle_real_time_data_window)
    self.diagnose_button.clicked.connect(self.toggle_diagnose)
    self.motion_params_button.clicked.connect(self.toggle_motion_params)
    self.q_params_button.clicked.connect(self.toggle_q_params)
    self.view_button.clicked.connect(self.toggle_view)

    self.input_pos_a.valueChanged.connect(self.set_target_pos_a)
    self.input_pos_e.valueChanged.connect(self.set_target_pos_e)
    self.input_active_frame.valueChanged.connect(self.set_active_frame)
    self.input_right_ascension.valueChanged.connect(self.set_ra)
    self.input_declination.valueChanged.connect(self.set_dec)
    self.input_speed.valueChanged.connect(self.set_speed)


    self.set_target_pos_a(float(self.input_pos_a.text().replace(",", ".")))
    self.set_target_pos_e(float(self.input_pos_e.text().replace(",", ".")))
    self.set_active_frame(float(self.input_active_frame.text().replace(",", ".")))
    self.set_ra(float(self.input_right_ascension.text().replace(",", ".")))
    self.set_dec(float(self.input_declination.text().replace(",", ".")))
    self.set_speed(float(self.input_speed.text().replace(",", ".")))

    self.stop_button.setEnabled(True)
    self.home_button.setEnabled(True)
    self.passiv_tracking_button.setEnabled(True)
    self.active_tracking_button.setEnabled(True)
    self.go_to_button.setEnabled(True)
    self.passiv_star_tracking_button.setEnabled(True)
    self.manual_remote_button.setEnabled(True)
    self.camera_button.setEnabled(True)

    self.real_time_data_button.setEnabled(True)
    self.diagnose_button.setEnabled(True)
    self.motion_params_button.setEnabled(True)
    self.q_params_button.setEnabled(True)
    self.view_button.setEnabled(True)

active_tracking(pos)

This function implements active tracking, which adjusts the system's motion based on feedback from the 4Q sensor. It calculates the differences ('DA' and 'DE') between the current position ('pos') and a reference position adjusted with an offset. It computes the orientation angles ('DAO' and 'DEO') from the 4Q sensor feedback and adjusts the system's motion using proportional-integral (PI) controllers for both azimuth and elevation axes, taking into account the active sensor feedback and the system's state. The function evaluates whether the system is within the active tracking frame and updates the tracking mode accordingly. If the system is out of frame, it switches to passive tracking. It then adjusts the system's motion based on the calculated speeds and updates the motion parameters ('M'). Finally, it updates the error values for logging purposes.

Parameters:

Name Type Description Default
pos dict

Current position of the system.

required

Returns:

Type Description

None

Source code in GUI.py
def active_tracking(self, pos):
    """
    This function implements active tracking, which adjusts the system's motion based on feedback from
    the 4Q sensor. It calculates the differences ('DA' and 'DE') between the current position ('pos')
    and a reference position adjusted with an offset. It computes the orientation angles ('DAO' and 'DEO')
    from the 4Q sensor feedback and adjusts the system's motion using proportional-integral (PI)
    controllers for both azimuth and elevation axes, taking into account the active sensor feedback
    and the system's state. The function evaluates whether the system is within the active tracking frame
    and updates the tracking mode accordingly. If the system is out of frame, it switches to passive tracking.
    It then adjusts the system's motion based on the calculated speeds and updates the motion parameters ('M').
    Finally, it updates the error values for logging purposes.

    Args:
        pos (dict): Current position of the system.

    Returns:
        None
    """

    #print(pos['FQ'][0])
    #print(pos['FQ'][1])

    DAO = np.arctan(pos['FQ'][0])/ np.sin(np.deg2rad(90-self.pos['E']))/2
    DEO = np.arctan(pos['FQ'][1])/2

    if DAO == np.nan:
        DAO = 0
    if DEO == np.nan:
        DEO = 0

    self.pos_and_speed()
    DA = pos['A'] - (self.pos1['A']-self.target_offset['A'])
    DE = pos['E'] - (90+(self.pos1['E']-self.target_offset['E']))/2

    self.passive_speed['A'] = 2*self.passive_speed['A']

    self.activePI_A.feed(DAO, self.passive_speed['A'])
    self.activePI_E.feed(DEO, self.passive_speed['E'])

    if abs(DE) < self.active_frame and abs(DA) < self.active_frame:
        self.active_tracking_condition = True
    else:
        self.active_tracking_condition = False


    if self.active_tracking_condition:
        if np.all(self.activePI_E.array == 0):
            self.real_time_data_window.update_mode(f"No active signal, tracking still in frame {self.active_frame} deg")
        else:
            self.real_time_data_window.update_mode(f"Active tracking in frame, frame size {self.active_frame} deg")    
    else:
        self.real_time_data_window.update_mode("Out of frame: switch to passive tracking")
        #self.state = 3 # Fraglich ob überhaupt benötigt wird weil automatischer wechsel zwischen active und passive tracking stattfindet in der active tracking funktion

    if DEO < 0 and DEO < -2:
        self.M['E'] = self.M_max
    elif DEO > 0 and DEO > 2:
        self.M['E'] = -self.M_max
    elif self.active_tracking_condition == False:
        self.M['E'] = -(self.passive_speed['E']*self.grad2step-DE*self.grad2step*self.M_slow_rel)
    else:
        self.M['E'] = -self.activePI_E.get_speed()

    if DAO < 0 and DAO < -2:
        self.M['A'] = -self.M_max
    elif DAO > 0 and DAO > 2:
        self.M['A'] = self.M_max
    elif self.active_tracking_condition == False:
        self.M['A'] = self.passive_speed['A']*self.grad2step-DA*self.grad2step*self.M_slow_rel_Az
    elif abs(DA) > 0.2:
        self.M['A'] = self.passive_speed['A']*self.grad2step+DAO*self.grad2step*self.M_slow_rel_Az
    else:
        self.M['A'] = self.activePI_A.get_speed()

    self.E['DA'] = DA
    self.E['DE'] = DE

closeEvent(event)

This function handls the close event of the main window.

Parameters:

Name Type Description Default
event

The close event triggered by closing the main window.

required

Returns:

Type Description

None

Source code in GUI.py
def closeEvent(self,event):
    """
    This function handls the close event of the main window.

    Args:
        event: The close event triggered by closing the main window.

    Returns:
        None
    """
    self.real_time_data_window.close()
    self.motion_params.close()
    self.q_params.close()
    self.diagnose.close()
    self.spatial_view.close()
    self.timer.stop()
    self.timer_fourq.stop()
    self.fourq.delete()
    self.io.delete()
    event.accept()

fixed_target()

This function calculates the motion required to move the system towards a fixed target position. It uses the 'go_to_target' function to initiate movement towards the target position based on the current position ('pos') and the target position ('target'). It updates the motion parameters in ('M') and updates the real-time data window to indicate that the system is moving towards the target position.

Returns:

Name Type Description
dict

The motion parameters required to move towards the fixed target position.

Source code in GUI.py
def fixed_target(self):
    """
    This function calculates the motion required to move the system towards a fixed target position.
    It uses the 'go_to_target' function to initiate movement towards the target position based on the
    current position ('pos') and the target position ('target'). It updates the motion parameters in ('M')
    and updates the real-time data window to indicate that the system is moving towards the target position.

    Args:
        None

    Returns:
        dict: The motion parameters required to move towards the fixed target position.
    """
    self.M = self.go_to_target(self.pos, self.target['A'], self.target['E'])
    mode = f"Moving towards {self.target['A']}/{self.target['E']}"
    self.real_time_data_window.update_mode(mode)

get_angles(time)

This function calculates the solar azimuth and zenith angles based on the given time. It uses the 'get_solarposition' function to obtain the solar position for the specified time and location. The azimuth and zenith angles are extracted from the solar position data.

Parameters:

Name Type Description Default
time datetime

The time at which the solar angles are to be calculated.

required

Returns:

Name Type Description
tuple

A tuple containing the solar azimuth and zenith angles.

Source code in GUI.py
def get_angles(self, time):
    """
    This function calculates the solar azimuth and zenith angles based on the given time.
    It uses the 'get_solarposition' function to obtain the solar position for the specified time
    and location. The azimuth and zenith angles are extracted from the solar position data.

    Args:
        time (datetime): The time at which the solar angles are to be calculated.

    Returns:
        tuple: A tuple containing the solar azimuth and zenith angles.
    """
    timestamp = pd.Timestamp(time, tz='Europe/Zurich')
    solar_position = get_solarposition(timestamp, self.latitude, self.longitude, self.altitude)
    azimuth = solar_position['azimuth'].values
    zenith = solar_position['apparent_zenith'].values
    return azimuth,zenith

get_angles_star(target_ra, target_dec)

This function calculates the azimuth and zenith angles for a celestial object based on its right ascension (RA) and declination (Dec). It creates a SkyCoord object for the specified RA and Dec, transforms it to an AltAz frame, and obtains the azimuth and altitude angles. The calculated azimuth and zenith angles are returned.

Parameters:

Name Type Description Default
target_ra float

Right ascension of the target celestial object (in degrees).

required
target_dec float

Declination of the target celestial object (in degrees).

required

Returns:

Name Type Description
tuple

A tuple containing the azimuth and zenith angles of the celestial object.

Source code in GUI.py
def get_angles_star(self, target_ra, target_dec):
    """
    This function calculates the azimuth and zenith angles for a celestial object based on
    its right ascension (RA) and declination (Dec). It creates a SkyCoord object for the specified
    RA and Dec, transforms it to an AltAz frame, and obtains the azimuth and altitude angles.
    The calculated azimuth and zenith angles are returned.

    Args:
        target_ra (float): Right ascension of the target celestial object (in degrees).
        target_dec (float): Declination of the target celestial object (in degrees).

    Returns:
        tuple: A tuple containing the azimuth and zenith angles of the celestial object.
    """
    position = SkyCoord(ra=target_ra*u.degree, dec=target_dec*u.degree, frame='icrs')
    location = EarthLocation(lat=self.latitude*u.deg, lon=self.longitude*u.deg, height=self.altitude*u.m)
    time = Time(datetime.now())

    altaz = position.transform_to(AltAz(obstime=time,location=location))
    alt = altaz.alt.deg
    az = altaz.az.deg
    return az, alt

go_to_target(POS, TA, TE)

This function calculates the motion parameters required to move the system from its current position ('POS') towards a target position specified by the target angles ('TA' for primary axis and 'TE' for secondary axis). It computes the differences between the current position and the target position for both axes ('DA' and 'DE'). Based on these differences, it determines the motion required for each axis ('M['A']' and 'M['E']'). If the difference in position is significant (> 2 degrees), the maximum speed ('M_max') is used. Otherwise, a slower speed ('M_slow_fixed_target') is calculated proportionally to the difference. If the difference is very small (< 0.01 degrees), the motion for that axis is set to 0. The calculated motion parameters are returned.

Parameters:

Name Type Description Default
POS dict

Current position of the system

required
TA float

Target angle for the primary axis.

required
TE float

Target angle for the secondary axis.

required

Returns:

Name Type Description
dict

The motion parameters required to move towards the target position.

Source code in GUI.py
def go_to_target(self, POS,TA,TE):
    """
    This function calculates the motion parameters required to move the system from its current position ('POS')
    towards a target position specified by the target angles ('TA' for primary axis and 'TE' for secondary axis).
    It computes the differences between the current position and the target position for both axes ('DA' and 'DE').
    Based on these differences, it determines the motion required for each axis ('M['A']' and 'M['E']').
    If the difference in position is significant (> 2 degrees), the maximum speed ('M_max') is used.
    Otherwise, a slower speed ('M_slow_fixed_target') is calculated proportionally to the difference.
    If the difference is very small (< 0.01 degrees), the motion for that axis is set to 0.
    The calculated motion parameters are returned.

    Args:
        POS (dict): Current position of the system
        TA (float): Target angle for the primary axis.
        TE (float): Target angle for the secondary axis.

    Returns:
        dict: The motion parameters required to move towards the target position.
    """
    DA = POS['A'] - TA
    DE = POS['E'] - TE

    if DE < 0 and DE < -2:
        self.M['E'] = -self.M_max
    elif DE > 0 and DE > 2:
        self.M['E'] = self.M_max
    else:
        self.M['E'] = DE * self.M_slow_fixed_target * self.grad2step

    if DA < 0 and DA < -2:
        self.M['A'] = self.M_max
    elif DA > 0 and DA > 2:
        self.M['A'] = -self.M_max
    elif 0 <= DA < 0.01:
        self.M['A'] = 0
    else:
        self.M['A'] = DA * -self.M_slow_fixed_target * self.grad2step
    return self.M

home(pos)

This function checks if the system is at the home position by comparing the current position with the predefined home position with a tolerance of +/- 0.1 degrees for both primary and secondary axes. If the system is at the home position, it sets the state to 0 (stopped) and updates the real-time data window to indicate that the home position is reached and the motors are off. If the system is not at the home position, it initiates movement towards the home position using the 'go_to_target' function and updates the real-time data window to indicate that the system is moving towards the home position.

Parameters:

Name Type Description Default
pos dict

Current position of the system.

required

Returns:

Type Description

None

Source code in GUI.py
def home(self, pos):
    """
    This function checks if the system is at the home position by comparing the current
    position with the predefined home position with a tolerance of +/- 0.1 degrees for
    both primary and secondary axes. If the system is at the home position, it sets the
    state to 0 (stopped) and updates the real-time data window to indicate that the
    home position is reached and the motors are off. If the system is not at the home
    position, it initiates movement towards the home position using the 'go_to_target'
    function and updates the real-time data window to indicate that the system is
    moving towards the home position.

    Args:
        pos (dict): Current position of the system.

    Returns:
        None
    """

    if self.home_pos['E']-0.1 < pos['E'] < self.home_pos['E']+ 0.1 and self.home_pos['A'] - 0.1 < pos['A'] < self.home_pos['A'] + 0.1:
        self.state = 0
        self.real_time_data_window.update_mode("Home position reached, motors off")
    else:
        self.go_to_target(pos, self.home_pos['A'],self.home_pos['E'])
        self.real_time_data_window.update_mode("Moving towards home position")

manual_control()

This function enables manual control of the system based on user input. It requests manual input from the input/output interface and adjusts the system's motion accordingly. It checks the status of each motor and adjusts the system's motion speed based on the received commands. The motion parameters ('M') are updated accordingly for both azimuth ('A') and elevation ('E') axes.

Returns:

Type Description

None

Source code in GUI.py
def manual_control(self):
    """
    This function enables manual control of the system based on user input. It requests manual input
    from the input/output interface and adjusts the system's motion accordingly.
    It checks the status of each motor and adjusts the system's motion speed based on the received commands.
    The motion parameters ('M') are updated accordingly for both azimuth ('A') and elevation ('E') axes.

    Args:
        None

    Returns:
        None
    """
    self.io.request_manual_input()
    #print(self.io.M_rep_M)
    try:
        statusE = bin(int(self.io.M_rep_M['E']))
        if statusE[-1] == "1":
            #print("ror")
            self.M['E'] = -self.speed
        elif statusE[-2] == "1":
            #print("rol")
            self.M['E'] = self.speed
        else:
            #print("stop")
            self.M['E'] = 0
    except:
        print("No valid reply from motor E")
        self.M['E'] = 0

    try:
        statusA = bin(int(self.io.M_rep_M['A']))
        if statusA[-1] == "1":
            #print("ror")
            self.M['A'] = -self.speed
        elif statusA[-2] == "1":
            #print("rol")
            self.M['A'] = self.speed
        else:
            #print("stop")
            self.M['A'] = 0
    except:
        print("No valid reply from motor A")
        self.M['A'] = 0

passive_tracking(pos)

This function implements passive tracking, which adjusts the system's motion based on the difference between the current position and a reference position (position that directs the beam of the sun into the laboratory). It calculates the differences ('DA' and 'DE') between the current position ('pos') and the reference position adjusted with an offset. It then adjusts the system's motion using proportional-integral (PI) controllers for both azimuth and elevation axes, taking into account the passive speed settings and the current state of the system. The system adjusts its motion differently based on whether the differences are significant or within fine-tuning thresholds. It updates the system's state and motion parameters accordingly and updates the real-time data window to indicate the tracking mode.

Parameters:

Name Type Description Default
pos dict

Current position of the system.

required

Returns:

Type Description

None

Source code in GUI.py
def passive_tracking(self, pos):
    """  
    This function implements passive tracking, which adjusts the system's motion based on the difference
    between the current position and a reference position (position that directs the beam of the sun into the laboratory). 
    It calculates the differences ('DA' and 'DE') between the current position ('pos') and the reference position adjusted with an offset.
    It then adjusts the system's motion using proportional-integral (PI) controllers for both azimuth and
    elevation axes, taking into account the passive speed settings and the current state of the system.
    The system adjusts its motion differently based on whether the differences are significant or within
    fine-tuning thresholds. It updates the system's state and motion parameters accordingly and updates
    the real-time data window to indicate the tracking mode.

    Args:
        pos (dict): Current position of the system.

    Returns:
        None
    """
    self.pos_and_speed()
    DA = pos['A'] - (self.pos1['A']-self.target_offset['A'])
    DE = pos['E'] - (90+(self.pos1['E']-self.target_offset['E']))/2

    self.passive_speed['A'] = 2*self.passive_speed['A']
    self.passivPI_E.feed(DA, self.passive_speed['A'])
    self.passivPI_A.feed(DE, self.passive_speed['E'])

    #Elevation
    if DE < 0 and DE < -1:
        self.M['E'] = -self.M_max
        self.state_secondary = 1
    elif DE > 0 and DE > 1:
        self.M['E'] = self.M_max
        self.state_secondary = 1
    elif DE > 0.01 or DE < -0.01:
        self.state_secondary = 0
        self.M['E'] = -(self.passive_speed['E']*self.grad2step-DE*self.grad2step*self.M_slow_rel)
    else:
        self.state_secondary = 0
        self.M['E'] = -self.passivPI_E.get_speed()

    #Azimuth   
    if DA < 0 and DA < -1:
        self.state_primary = 1
        self.M['A'] = self.M_max
    elif DA > 0 and DA > 1:
        self.state_primary = 1
        self.M['A'] = -self.M_max 
    elif DA > 0.01 or DA <-0.01:
        self.state_primary = 0
        self.M['A'] = self.passive_speed['A']*self.grad2step-DA*self.grad2step*self.M_slow_rel_Az
    else:
        self.state_primary = 0
        self.M['A'] = self.passivPI_A.get_speed()

    if self.state_secondary == 1 and self.state_primary == 1:
        self.real_time_data_window.update_mode("Passive tracking: coarse")

    elif self.state_secondary == 0 and self.state_primary == 0:
        self.real_time_data_window.update_mode("Passive tracking: fine")


    self.E['DA'] = DA
    self.E['DE'] = DE

pos_and_speed()

This function calculates the current position and speed of the system based on the current time. It obtains the azimuth and zenith angles at two consecutive time points and converts them into the system's coordinate system using polar rotation. It then calculates the speed of the system by taking the difference in position between the two time points which is further used in passive tracking.

Returns:

Type Description

None

Source code in GUI.py
def pos_and_speed(self):
    """
    This function calculates the current position and speed of the system based on the current time.
    It obtains the azimuth and zenith angles at two consecutive time points and converts them into
    the system's coordinate system using polar rotation. It then calculates the speed of the system
    by taking the difference in position between the two time points which is further used in passive tracking. 

    Args:
        None

    Returns:
        None
    """

    now = datetime.now()
    azimuth, zenith = self.get_angles(now)
    self.pos1 = PRM.polar_rotation(self, azimuth,90-zenith, self.trafoA, self.trafoE)

    now1 = datetime.now() + timedelta(seconds=1)
    azimuth, zenith = self.get_angles(now1)
    self.pos2 = PRM.polar_rotation(self, azimuth,90-zenith, self.trafoA, self.trafoE)

    self.passive_speed['A'] =  self.pos2['A'] - self.pos1['A']   # speed calculation in grad/second (important for PI Controller afterwards)
    self.passive_speed['E'] =  self.pos2['E'] - self.pos1['E']   # speed calculation in grad/second (important for PI Controller afterwards)

set_active_frame(value)

Set the maximum aloud error for active tracking

Parameters:

Name Type Description Default
value float

The maximum error to set for active tracking

required

Returns:

Type Description

None

Source code in GUI.py
def set_active_frame(self, value):
    """
    Set the maximum aloud error for active tracking

    Args:
        value (float): The maximum error to set for active tracking

    Returns:
        None
    """
    self.active_frame = value

set_dec(value)

Set the declination (DEC) target value.

Parameters:

Name Type Description Default
value float

The value to set as the target DEC.

required

Returns:

Type Description

None

Source code in GUI.py
def set_dec(self, value):
    """
    Set the declination (DEC) target value.

    Args:
        value (float): The value to set as the target DEC.

    Returns:
        None
    """
    self.target_dec = value

set_ra(value)

Set the right ascension (RA) target value.

Parameters:

Name Type Description Default
value float

The value to set as the target RA.

required

Returns:

Type Description

None

Source code in GUI.py
def set_ra(self, value):
    """
    Set the right ascension (RA) target value.

    Args:
        value (float): The value to set as the target RA.

    Returns:
        None
    """
    self.target_ra = value

set_speed(value)

Set the speed for manual remote controll

Parameters:

Name Type Description Default
value float

The speed to set for the manual remote mode

required

Returns:

Type Description

None

Source code in GUI.py
def set_speed(self, value):
    """
    Set the speed for manual remote controll

    Args:
        value (float): The speed to set for the manual remote mode

    Returns:
        None
    """
    self.speed = value

set_target_pos_a(value)

Set the target position for the primary axis

Parameters:

Name Type Description Default
value float

The position to set for the primary axis

required

Returns:

Type Description

None

Source code in GUI.py
def set_target_pos_a(self, value):
    """
    Set the target position for the primary axis

    Args:
        value (float): The position to set for the primary axis

    Returns:
        None
    """
    self.target['A'] = value

set_target_pos_e(value)

Set the target position for the secondary axis

Parameters:

Name Type Description Default
value float

The position to set for the second axis

required

Returns:

Type Description

None

Source code in GUI.py
def set_target_pos_e(self, value):
    """
    Set the target position for the secondary axis

    Args:
        value (float): The position to set for the second axis

    Returns:
        None
    """
    self.target['E'] = value

star_tracking(pos)

This function implements star tracking, which adjusts the system's motion to track a celestial object based on its right ascension (RA) and declination (Dec). It calculates the azimuth and zenith angles of the star using the target RA and Dec and transforms them into the system's coordinate system. The function then calculates the differences ('DA' and 'DE') between the current position ('pos') and the calculated position of the star. Based on these differences, it adjusts the system's motion to track the star using proportional control. The motion parameters ('M') are updated accordingly.

Parameters:

Name Type Description Default
pos dict

Current position of the system.

required

Returns:

Type Description

None

Source code in GUI.py
def star_tracking(self, pos):
    """
    This function implements star tracking, which adjusts the system's motion to track a celestial object
    based on its right ascension (RA) and declination (Dec). It calculates the azimuth and zenith
    angles of the star using the target RA and Dec and transforms them into the system's coordinate system.
    The function then calculates the differences ('DA' and 'DE') between the current position ('pos') and
    the calculated position of the star. Based on these differences, it adjusts the system's motion to
    track the star using proportional control. The motion parameters ('M') are updated accordingly.

    Args:
        pos (dict): Current position of the system.

    Returns:
        None
    """
    azimuth, zenith = self.get_angles_star(self.target_ra, self.target_dec)
    star = {'A': azimuth, 'E':90-zenith}
    Heli_star = PRM.polar_rotation(star['A'],90-star['E'], self.trafoA, self.trafoE)
    DA = pos['A'] - Heli_star['A']
    DE = pos['E'] - (90+Heli_star['E'])/2


    #Fraglich ob star tracking so funktioniert oder besser gleiches prinzip wie im passive tracking
    if DE < 0 and DE < -1:
        self.M['E'] = -self.M_max   
    elif DE > 0 and DE > 1:
        self.M['E'] = self.M_max
    else:
        self.M['E'] = DE * -self.M_slow_rel

    if DA < 0 and DA < -1:
        self.M['E'] = -self.M_max   
    elif DA > 0 and DA > 1:
        self.M['E'] = self.M_max
    else:
        self.M['E'] = DA * -self.M_slow_rel

stop()

This function stops the motion of the system.

Returns:

Type Description

None

Source code in GUI.py
def stop(self):
    """
    This function stops the motion of the system.

    Args:
        None

    Returns:
        None
    """
    self.M['E'] = 0
    self.M['A'] = 0

toggle_active_tracking()

Toggle to change the current mode to active tracking

Returns:

Type Description

None

Source code in GUI.py
def toggle_active_tracking(self):
    """
    Toggle to change the current mode to active tracking

    Returns:
        None
    """
    self.mode.setText("Active tracking")
    self.state = 4

toggle_camera()

Toggle to open the site with access to the camera that is installed remotly.

Returns:

Type Description

None

Source code in GUI.py
def toggle_camera(self):
    """
    Toggle to open the site with access to the camera that is installed remotly. 

    Returns:
        None
    """
    url = 'http://srv-wrc-syno2.ad.pmodwrc.ch:5000/index.cgi?launchApp=SYNO.SDS.SurveillanceStation#/signin'
    webbrowser.open(url)

toggle_diagnose()

Toggles for the diagnose data plot.

Returns:

Type Description

None

Source code in GUI.py
def toggle_diagnose(self):
    """
    Toggles for the diagnose data plot.

    Returns:
        None
    """
    if self.diagnose.isVisible():
        self.diagnose.close()
    else:
        self.diagnose.show()

toggle_go_to()

Toggle to change the current mode to going to a certain position

Returns:

Type Description

None

Source code in GUI.py
def toggle_go_to(self):
    """
    Toggle to change the current mode to going to a certain position

    Returns:
        None
    """
    self.mode.setText("Fixed position")
    self.state = 2

toggle_home()

Toggle to change the current mode to home

Returns:

Type Description

None

Source code in GUI.py
def toggle_home(self):
    """
    Toggle to change the current mode to home

    Returns:
        None
    """
    self.mode.setText("Home")
    self.state = 1

toggle_manual_remote()

Toggle to change the current mode to manual remote

Returns:

Type Description

None

Source code in GUI.py
def toggle_manual_remote(self):
    """
    Toggle to change the current mode to manual remote

    Returns:
        None
    """
    self.mode.setText("Manual remote")
    self.state = 6

toggle_motion_params()

Toggle for the motion parameters plot.

Returns:

Type Description

None

Source code in GUI.py
def toggle_motion_params(self):
    """
    Toggle for the motion parameters plot.

    Returns:
        None
    """
    if self.motion_params.isVisible():
        self.motion_params.close()
    else:
        self.motion_params.show()

toggle_passive_star_tracking()

Toggle to change the current mode to star tracking

Returns:

Type Description

None

Source code in GUI.py
def toggle_passive_star_tracking(self):
    """
    Toggle to change the current mode to star tracking

    Returns:
        None
    """
    self.mode.setText("Passive star tracking")
    self.state = 5

toggle_passive_tracking()

Toggle to change the current mode to passive tracking

Returns:

Type Description

None

Source code in GUI.py
def toggle_passive_tracking(self):
    """
    Toggle to change the current mode to passive tracking

    Returns:
        None
    """
    self.mode.setText("Passive tracking")
    self.state = 3

toggle_q_params()

Toggle for the 4Q sensor data plot.

Returns:

Type Description

None

Source code in GUI.py
def toggle_q_params(self):
    """
    Toggle for the 4Q sensor data plot.

    Returns:
        None
    """
    if self.q_params.isVisible():
        self.q_params.close()
    else:
        self.q_params.show()

toggle_real_time_data_window()

Toggles for the real time data plot.

Returns:

Type Description

None

Source code in GUI.py
def toggle_real_time_data_window(self):
    """
    Toggles for the real time data plot.

    Returns:
        None
    """
    if self.real_time_data_window.isVisible():
        self.real_time_data_window.close()
    else:
        self.real_time_data_window.show()

toggle_stop()

Toggle to change the current mode to stop

Returns:

Type Description

None

Source code in GUI.py
def toggle_stop(self):
    """
    Toggle to change the current mode to stop

    Returns:
        None
    """
    self.mode.setText("Stop")
    self.state = 0

toggle_view()

Toggle for the 3D/2D view.

Returns:

Type Description

None

Source code in GUI.py
def toggle_view(self):
    """
    Toggle for the 3D/2D view.

    Returns:
        None
    """
    if self.spatial_view.isVisible():
        self.spatial_view.close()
    else:
        self.spatial_view.show()

update_plots()

This function updates the plots displayed in different widgets with new data. It appends the current time value to the time vector and updates the diagnose plot, motion parameters plot, 4Q plot, and spatial view plot with the updated data.

Returns:

Type Description

None

Source code in GUI.py
def update_plots(self):
    """
    This function updates the plots displayed in different widgets with new data.
    It appends the current time value to the time vector and updates the diagnose plot,
    motion parameters plot, 4Q plot, and spatial view plot with the updated data.

    Args:
        None

    Returns:
        None
    """
    self.t_vector.append(self.t)
    if len(self.t_vector) == 100:
        self.t_vector = self.t_vector[1:]
    self.diagnose.update_diagnose_plot(self.t_vector,self.M)
    self.motion_params.update_motion_plot(self.t_vector, self.pos, self.E)  
    self.q_params.update_fourq_plot(self.t_vector,self.pos)
    self.spatial_view.update_view_plot(self.pos)     

update_state()

This function updates the state of the system according to the current state value. It performs various actions based on the state, such as stopping motion, going to the home position, going to a fixed target, passive sun tracking, active sun tracking, star tracking, manual remote control, and logging data. It also updates real-time data plots, sends speed data to the motor and performs limit checks to prevent reaching limit positions which could damage the mirror or cables.

Returns:

Type Description

None

Source code in GUI.py
def update_state(self):
    """
    This function updates the state of the system according to the current state value.
    It performs various actions based on the state, such as stopping motion, going to the home position,
    going to a fixed target, passive sun tracking, active sun tracking, star tracking, manual remote control,
    and logging data. It also updates real-time data plots, sends speed data to the motor
    and performs limit checks to prevent reaching limit positions which could damage the mirror or cables.

    Args:
        None

    Returns:
        None

    """
    self.timer_count += 1
    self.pos = self.io.getdata()

    if self.state == 0:
        self.stop()
        self.real_time_data_window.update_mode("Stop") 
    elif self.state == 1:
        self.home(self.pos)
    elif self.state == 2:
        self.fixed_target() 
    elif self.state == 3:
        self.passive_tracking(self.pos)
    elif self.state == 4:
        self.active_tracking(self.pos)

    elif self.state == 5:
        self.star_tracking(self.pos)
        print("star tracking")
    elif self.state == 6:
        self.manual_control()
    elif self.state == 7:
        self.stop() 
    self.real_time_data_window.update_variables(self.M,self.pos)
    self.t = self.timer_count*self.update_period/3600+self.t0
    self.update_plots()
    self.soft_end_switch = self.io.send_data(self.M)
    self.io.request_motor_data()
    self.logger.log(self.t,self.state,self.pos,self.M,self.E)


    if self.soft_end_switch == 1:
        self.state = 0
        self.real_time_data_window.update_mode("Reached soft end switch") 



    #Making a limit check to prevent from limit positions 
    if self.pos['A'] < self.low_limit_A and self.M['A'] < 0:
        self.state = 7
    elif self.pos['A'] > self.high_limit_A and self.M['A'] > 0:
        self.state = 7 

    if self.pos['E'] < self.low_limit_E and self.M['E'] < 0:
        self.state = 7
    elif self.pos['E'] > self.high_limit_E and self.M['E'] > 0:
        self.state = 7