diff --git a/domehunter/dome_control.py b/domehunter/dome_control.py index ac40123..1c6ab9e 100644 --- a/domehunter/dome_control.py +++ b/domehunter/dome_control.py @@ -3,6 +3,7 @@ import math import sys +import threading import time import warnings import logging @@ -157,7 +158,7 @@ def __init__(self, # pin factory to release all the pins Device.pin_factory.reset() # set a timeout length in seconds for wait_for_active() calls - WAIT_TIMEOUT = 1 + WAIT_TIMEOUT = 2*60 # in testing mode we need to create a seperate pin object so we can # simulate the activation of our fake DIDs and DODs @@ -179,15 +180,11 @@ def __init__(self, # TODO: read in default value from yaml(?) self._degrees_per_tick = Angle(degrees_per_tick * u.deg) - self.az_position_tolerance = Angle(az_position_tolerance * u.deg) - # if the requested tolerance is less than degrees_per_tick use - # degrees_per_tick for the tolerance - self.az_position_tolerance = max( - self.az_position_tolerance, self.degrees_per_tick) + self._az_position_tolerance = Angle(az_position_tolerance * u.deg) self.home_az = Longitude(home_azimuth * u.deg) # need something to let us know when dome is calibrating so home sensor # activation doesnt zero encoder counts - self.calibrating = False + self._calibrating = False # NOTE: this led setup needs to be done before setting any callback # functions @@ -214,6 +211,14 @@ def __init__(self, # create a instance variable to track the dome motor encoder ticks self._encoder_count = 0 self._dome_az = None # Unknown + # create a threading abort event, to use for aborting movement commands + self._abort_event = threading.Event() + # creating a threading move event, to indicate when a move thread + # is active + self._move_event = threading.Event() + # creating a threading simulated_rotation event, to indicate when a + # simulated rotation thread is running for testing mode calibration + self._simulated_rotation_event = threading.Event() # bounce_time settings gives the time in seconds that the device will # ignore additional activation signals logger.info(f'Connecting encoder on pin {encoder_pin_number}') @@ -238,6 +243,8 @@ def __init__(self, # because we initialiase the relay in the normally closed position logger.info(f'Setting start direction to CCW') self.current_direction = Direction.CCW + # need to set something for last direction now + self.last_direction = Direction.NONE # Home Sensor logger.info(f'Connecting home sensor on pin {home_sensor_pin_number}') @@ -252,14 +259,13 @@ def __init__(self, else: self._set_not_home() - ############################################################################### # Properties ############################################################################### @property def dome_az(self): - """ """ + """Returns the dome azimuth in degrees.""" if self._dome_az is None: print("Cannot return Azimuth as Dome is not yet calibrated. Run calibration loop") logger.debug(f'Dome azimuth: {self._dome_az}') @@ -267,7 +273,7 @@ def dome_az(self): @property def at_home(self): - """Send True if the dome is at home.""" + """Return True if the dome is at home.""" home_active = self._home_sensor.is_active logger.debug(f'Home active: {home_active}') return home_active @@ -279,6 +285,11 @@ def dome_in_motion(self): logger.debug(f'Dome in motion: {dome_motion}') return dome_motion + @property + def movement_thread_active(self): + """Return true if a movement thread is running""" + return self._move_event.is_set() + @property def encoder_count(self): """Returns the current encoder count.""" @@ -290,11 +301,25 @@ def degrees_per_tick(self): """Returns the calibrated azimuth (in degrees) per encoder tick.""" logger.debug(f'Degrees per tick: {self._degrees_per_tick}') return self._degrees_per_tick + + @property + def az_position_tolerance(self): + """ + Returns the azimuth position tolerance (in degrees). + + If the tolerance set at initialisation is less than degrees_per_tick + use 1.5 * degrees_per_tick as the tolerance. + """ + if self._az_position_tolerance < 1.5 * self.degrees_per_tick: + logger.warning(f'az_position_tolerance [{self._az_position_tolerance}] is less than 1.5 times degrees_per_tick. Setting tolerance to 1.5 * degrees_per_tick') + tolerance = max(self._az_position_tolerance, + 1.5 * self.degrees_per_tick) + return tolerance ############################################################################### # Methods ############################################################################### - """These map directly onto the AbstractMethods created by RPC.""" + """These correspond to the AbstractMethods created by RPC.""" def abort(self): """ @@ -305,7 +330,12 @@ def abort(self): # one way might be cut power to the automationHAT so the motor relays # will receive no voltage even if the relay is in the open position? logger.warning(f'Aborting dome movement') - self._stop_moving() + # set the abort event thread flag + self._abort_event.set() + # wait for threads to abort + while self.movement_thread_active: + time.sleep(0.1) + self._abort_event.clear() def goto_az(self, az): """ @@ -319,31 +349,27 @@ def goto_az(self, az): """ if self.dome_az is None: return + if self.movement_thread_active: + logger.warning('Movement command in progress') + return target_az = Longitude(az * u.deg) - logger.debug(f'Goto target azimuth: {target_az}') + logger.debug(f'goto_az: Goto target azimuth [{target_az}]') # calculate delta_az, wrapping at 180 to ensure we take shortest route delta_az = (target_az - self.dome_az).wrap_at(180 * u.degree) - logger.debug(f'Delta azimuth: {delta_az}') + logger.debug(f'goto_az: Delta azimuth [{delta_az}]') + self._move_event.set() if delta_az > 0: self._rotate_dome(Direction.CW) else: self._rotate_dome(Direction.CCW) # wait until encoder count matches desired delta az - while True: - delta_az = self.current_direction * (target_az - self.dome_az).wrap_at('180d') - if delta_az <= self.az_position_tolerance: - break - if self.testing: - # if testing simulate a tick for every cycle of while loop - self._simulate_ticks(num_ticks=1) - - time.sleep(0.1) - - logger.debug('Stopping dome movement') - self._stop_moving() + goingto_az = threading.Thread(target=self._thread_condition, + args=(self._goto_az_complete, + target_az)) + goingto_az.start() def calibrate_dome_encoder_counts(self, num_cal_rotations=2): """ @@ -355,63 +381,68 @@ def calibrate_dome_encoder_counts(self, num_cal_rotations=2): Number of rotations to perform to calibrate encoder. """ + if self.movement_thread_active: + logger.warning('Movement command in progress') + return # rotate the dome until we hit home, to give reference point + logger.debug(f'calibrate_dome_encoder_counts: Finding Home') self.find_home() - + while self.movement_thread_active: + # wait for find_home() to finish + time.sleep(0.1) + logger.debug(f'calibrate_dome_encoder_counts: Found Home') # pause to let things settle/get a noticeable blink of debug_lights time.sleep(0.5) - rotation_count = 0 - self.calibrating = True + # instance variable to track rotations during calibration + self._rotation_count = 0 + # instance variable that is over written in calibrate routine + self._num_cal_rotations = num_cal_rotations # now set dome to rotate num_cal_rotations times so we can determine # the number of ticks per revolution - while rotation_count < num_cal_rotations: - self._rotate_dome(Direction.CW) - if self.testing: - # tell the fake home sensor that we have left home - self._home_sensor_pin.drive_low() - self._simulate_ticks(num_ticks=10) - self._home_sensor.wait_for_inactive(timeout=self.wait_timeout) - self._home_sensor.wait_for_active(timeout=self.wait_timeout) - - if self.testing: - # tell the fake home sensor that we have come back to home - time.sleep(0.1) - self._home_sensor_pin.drive_high() - time.sleep(0.1) - - # without this pause the wait_for_active wasn't waiting (???) - time.sleep(0.1) - - rotation_count += 1 - - self._stop_moving() - - # set the azimuth per encoder tick factor based on how many ticks we - # counted over n rotations - self._degrees_per_tick = Angle( - 360 / (self.encoder_count / rotation_count) * u.deg) - # update the az_position_tolerance - self.az_position_tolerance = max( - self.az_position_tolerance, 2 * self.degrees_per_tick) - self.calibrating = False + logger.debug(f'calibrate_dome_encoder_counts: Starting calibration rotations.') + self._move_event.set() + self._rotate_dome(Direction.CW) + self._calibrating = True + calibrate = threading.Thread(target=self._simulate_calibration) + cal_monitor = threading.Thread(target=self._thread_condition, + args=(self._calibration_complete,)) + calibrate.start() + cal_monitor.start() def find_home(self): """ Move Dome to home position. """ + if self.movement_thread_active: + logger.warning('Movement command in progress') + return + # iniate the movement and set the _move_event flag + logger.debug(f'find_home: Finding Home') + self._move_event.set() self._rotate_dome(Direction.CW) time.sleep(0.1) - self._home_sensor.wait_for_active(timeout=self.wait_timeout) + homing = threading.Thread(target=self._thread_condition, + args=(self._find_home_complete,)) + homing.start() if self.testing: # in testing mode need to "fake" the activation of the home pin - time.sleep(0.5) - self._home_sensor_pin.drive_high() - - self._stop_moving() + home_pin_high = threading.Timer(0.5, + self._home_sensor_pin.drive_high) + home_pin_high.start() def sync(self, az): + """ + Sync encoder count to azimuth position. + + Parameters + ---------- + az : float + Current dome azimuth position in degrees. + + """ + logger.debug(f'sync: syncing encoder counts to azimuth [{az}]') self._encoder_count = self._az_to_ticks(Longitude(az * u.deg)) return @@ -419,17 +450,116 @@ def sync(self, az): # Private Methods ############################################################################### + def _thread_condition(self, trigger_condition, *args, **kwargs): + """Condition monitoring thread for movement commands. + + Will return when: + - trigger_condition is true + - abort event is triggered + - thread running time exceeds self.wait_timeout + + Parameters + ---------- + trigger_condition : callable method + Callable method that returns a boolean value. + + """ + calibration_success = False + # This will update to false, if while loop is terminated by + # trigger_condition() + # TODO: better system for flagging success/abort/timeout + goingtoaz = False + if trigger_condition.__name__ == '_goto_az_complete': + goingtoaz = True + start = time.monotonic() + + while True: + wait_time = time.monotonic() - start + if trigger_condition(*args, **kwargs): + logger.debug(f'Monitoring thread triggered by {trigger_condition.__name__}') + calibration_success = True + break + elif self._abort_event.is_set(): + logger.debug(f'Monitoring thread triggered by abort event') + break + elif wait_time > self.wait_timeout: + logger.debug(f'Monitoring thread triggered by timeout [{self.wait_timeout}s]') + break + elif goingtoaz and self.testing is True: + # if testing simulate a tick for every cycle of while loop + self._simulate_ticks(num_ticks=1) + time.sleep(0.1) + + logger.debug('Monitor Thread: Stopping dome movement') + self._stop_moving() + + if trigger_condition.__name__ == '_calibration_complete': + # set the azimuth per encoder tick factor based on + # how many ticks we counted over n rotations + self._degrees_per_tick = Angle(360 / (self.encoder_count / + self._rotation_count) * u.deg) + # reset various dome state variables/events + self._move_event.clear() + self._calibrating = False + return + + def _goto_az_complete(self, target_az): + """Determines if current azimuth is within tolerance of target azimuth. + + Returns + ------- + bool + Returns true if self.dome_az is within tolerance of target azimuth. + + """ + raw_delta_az = (target_az - self.dome_az).wrap_at('180d') + delta_az = self.current_direction * raw_delta_az + logger.debug(f'Delta_az is {delta_az}, tolerance window is {self.az_position_tolerance}') + return delta_az <= self.az_position_tolerance + + def _find_home_complete(self): + """Return True if the dome is at home.""" + return self._home_sensor.is_active + + def _calibration_complete(self): + """Return True if desired number of calibration rotations completed.""" + logger.debug(f'cc Rotation count is [{self._rotation_count}], rotations to go [{self._num_cal_rotations - self._rotation_count}].') + return self._rotation_count >= self._num_cal_rotations + + def _simulate_calibration(self): + """Calibration method to be called from within a non-blocking thread""" + start = time.monotonic() + first_rotation = threading.Thread(target=self._simulate_rotation) + first_rotation.start() + + while True: + logger.debug(f'Loop {time.monotonic() - start}: rot_count [{self._rotation_count}], encoder_count [{self.encoder_count}]') + if not self._simulated_rotation_event.is_set(): + time.sleep(1) + logger.debug(f'Calibration monitor thread: Completion of simulated rotation detected, simulating next rotation.') + simulated_rotation = threading.Thread(target=self._simulate_rotation) + if self._calibration_complete(): + logger.debug(f'Calibration monitor thread: calibration rotations completed, ending calibration.') + break + simulated_rotation.start() + time.sleep(1) + def _set_at_home(self): """ Update home status to at home and debug LEDs (if enabled). """ - logger.debug('Stopping dome movement') + logger.info('Home sensor activated') self._change_led_state(1, leds=[LED_Lights.INPUT_2]) # don't want to zero encoder while calibrating # note: because Direction.CW is +1 and Direction.CCW is -1, need to # add 1 to self.current_direction, to get CCW to evaluate to False - if not self.calibrating and bool(self.current_direction + 1): + if not self._calibrating and bool(self.current_direction + 1): + logger.debug(f'_set_at_home: Passing home clockwise, zeroing encoder counts.') self._encoder_count = 0 + # if we are calibrating, increment the rotation count + if self._calibrating: + logger.debug(f'_set_at_home: Home triggered during calibration, incrementing calibration rotation count.') + self._rotation_count += 1 def _set_not_home(self): """ @@ -558,6 +688,7 @@ def _stop_moving(self): """ logger.debug(f'Turning off rotation relay') self._rotation_relay.off() + self._move_event.clear() # update the debug LEDs self._change_led_state(0, leds=[LED_Lights.RELAY_1_NO]) self._change_led_state(1, leds=[LED_Lights.RELAY_1_NC]) @@ -582,6 +713,16 @@ def _simulate_ticks(self, num_ticks): self._encoder_pin.drive_high() time.sleep(self.test_mode_delay_duration) + def _simulate_rotation(self, ticks_per_rotation=10): + """ + Method to simulate a complete dome rotation while in testing mode. + """ + self._simulated_rotation_event.set() + self._home_sensor_pin.drive_low() + self._simulate_ticks(ticks_per_rotation) + self._home_sensor_pin.drive_high() + self._simulated_rotation_event.clear() + def _change_led_state(self, desired_state, leds=[]): # pragma: no cover """ Method of turning a set of debugging LEDs on diff --git a/domehunter/tests/test_domehunter.py b/domehunter/tests/test_domehunter.py index 3a26053..efa74d2 100644 --- a/domehunter/tests/test_domehunter.py +++ b/domehunter/tests/test_domehunter.py @@ -1,5 +1,5 @@ import pytest - +import time from domehunter.dome_control import * @@ -22,7 +22,7 @@ def dome_az_90(scope='function'): def test_dome_initialisation(testing_dome): assert testing_dome.testing is True assert testing_dome.dome_in_motion is False - assert testing_dome.dome_az.degree == 0 + assert testing_dome.dome_az is None assert testing_dome.encoder_count == 0 assert testing_dome.degrees_per_tick == Angle(1 * u.deg) assert testing_dome.at_home is False @@ -43,34 +43,42 @@ def test_status(testing_dome): assert testing_dome.dome_in_motion is False -def test_abort(testing_dome): - # This test should test that GotoAz() is non-blocking - # but haven't implemented that yet - testing_dome._rotation_relay.on() - assert testing_dome.dome_in_motion is True - testing_dome.abort() - assert testing_dome.dome_in_motion is False +def test_abort(dome_az_90): + dome_az_90.goto_az(300) + time.sleep(0.5) + assert dome_az_90.movement_thread_active + assert dome_az_90.dome_in_motion + time.sleep(0.5) + dome_az_90.abort() + assert not dome_az_90.movement_thread_active + assert not dome_az_90.dome_in_motion -def test_dome_az(dome_az_90): - assert dome_az_90.dome_az == 90 - assert dome_az_90.dome_az == dome_az_90.dome_az.degree +def test_dome_az(dome_az_90, testing_dome): + assert dome_az_90.dome_az == Longitude(90 * u.deg) + assert testing_dome.dome_az is None -def test_GotoAz(dome_az_90): +def test_goto_az(dome_az_90): # test fixture has degrees_per_tick attribute of 10 - dome_az_90.GotoAz(300) - assert dome_az_90.dome_az == Angle(300 * u.deg) - assert dome_az_90.encoder_count == -6 - dome_az_90.GotoAz(2) - assert dome_az_90.dome_az == Angle(10 * u.deg) - assert dome_az_90.encoder_count == 1 + dome_az_90.goto_az(300) + while dome_az_90.movement_thread_active: + time.sleep(1) + assert dome_az_90.dome_az == Angle(310 * u.deg) + assert dome_az_90.encoder_count == -5 + dome_az_90.goto_az(2) + while dome_az_90.movement_thread_active: + time.sleep(1) + assert dome_az_90.dome_az == Angle(350 * u.deg) + assert dome_az_90.encoder_count == -1 @pytest.mark.calibrate def test_calibrate_dome_encoder_counts(testing_dome): testing_dome.calibrate_dome_encoder_counts() - assert testing_dome.dome_az == 0 + assert testing_dome.dome_az is None + while testing_dome.movement_thread_active: + time.sleep(1) assert testing_dome.encoder_count == 20 assert testing_dome.degrees_per_tick == Angle(36 * u.deg) diff --git a/examples/Untitled.ipynb b/examples/Untitled.ipynb deleted file mode 100644 index be6c6a5..0000000 --- a/examples/Untitled.ipynb +++ /dev/null @@ -1,245 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "from enum import Flag" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "led_status = 0b000000000000000000" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "led_lights_ind = {\n", - " 'power': 2,\n", - " 'comms': 3,\n", - " 'warn': 4,\n", - " 'input_1': 5,\n", - " 'input_2': 6,\n", - " 'input_3': 7,\n", - " 'relay_3_normally_closed': 8,\n", - " 'relay_3_normally_open': 9,\n", - " 'relay_2_normally_closed': 10,\n", - " 'relay_2_normally_open': 11,\n", - " 'relay_1_normally_closed': 12,\n", - " 'relay_1_normally_open': 13,\n", - " 'output_3': 14,\n", - " 'output_2': 15,\n", - " 'output_1': 16,\n", - " 'adc_3': 17,\n", - " 'adc_2': 18,\n", - " 'adc_1': 19\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "new_state = format(led_status, '#020b')" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0'" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list(new_state)[2]" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "class LED_lights(Flag):\n", - " POWER = 0b100000000000000000\n", - " COMMS = 0b010000000000000000\n", - " WARN = 0b001000000000000000\n", - " INPUT_1 = 0b000100000000000000\n", - " INPUT_2 = 0b000010000000000000\n", - " INPUT_3 = 0b000001000000000000\n", - " RELAY_3_NC = 0b000000100000000000\n", - " RELAY_3_NO = 0b000000010000000000\n", - " RELAY_2_NC = 0b000000001000000000\n", - " RELAY_2_NO = 0b000000000100000000\n", - " RELAY_1_NC = 0b000000000010000000\n", - " RELAY_1_NO = 0b000000000001000000\n", - " OUTPUT_3 = 0b000000000000100000\n", - " OUTPUT_2 = 0b000000000000010000\n", - " OUTPUT_1 = 0b000000000000001000\n", - " ADC_3 = 0b000000000000000100\n", - " ADC_2 = 0b000000000000000010\n", - " ADC_1 = 0b000000000000000001" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0b000000001010000000\n", - "0b000000001000000000\n", - "0b000000001000000000\n" - ] - } - ], - "source": [ - "ledstatus = LED_lights.relay_2_normally_closed | LED_lights.relay_1_normally_closed\n", - "print(format(ledstatus.value, '#020b'))\n", - "ledstatus&=(~LED_lights.relay_1_normally_closed)\n", - "print(format(ledstatus.value, '#020b'))\n", - "ledstatus&=(~LED_lights.relay_1_normally_closed)\n", - "print(format(ledstatus.value, '#020b'))" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0b000000001010000000'" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "format(ledstatus.value, '#020b')" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0b000000001000000000'" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "format(ledstatus2.value, '#020b')" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0b000000001000000000'" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "format(ledstatus3.value, '#020b')" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "metadata": {}, - "outputs": [], - "source": [ - "dt = 1" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a\n" - ] - } - ], - "source": [ - "if dt is 1:\n", - " print('a')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/dome_control_example.ipynb b/examples/dome_control_example.ipynb index 0d618a8..d79df06 100644 --- a/examples/dome_control_example.ipynb +++ b/examples/dome_control_example.ipynb @@ -6,11 +6,16 @@ "metadata": {}, "outputs": [], "source": [ + "\"\"\"\n", + "When the domehunter package tries to import the sn3218 library it will either find it isn't installed, \n", + "or it wont detect the hardware it is expecting in both cases a warning will be raised. If you are \n", + "testing without the automationHAT this warning can be ignored.\n", + "\"\"\"\n", + "\n", "from domehunter.dome_control import Dome\n", "import astropy.units as u\n", "from astropy.coordinates import Angle, Longitude\n", - "# When the domehunter package tries to import the sn3218 library it will either find it isn't installed, or it wont detect the hardware it is expecting\n", - "# in both cases a warning will be raised. If you are testing without the automationHAT this warning can be ignored." + "import time" ] }, { @@ -19,11 +24,11 @@ "metadata": {}, "outputs": [], "source": [ - "# testing=True means all the automationHAT functionality and the state of the GPIOzero pins will be mocked/simulated\n", - "# debug_lights=True means the automationHAT status LEDs will be enabled on the automationHAT. If you do not have an automationHAT this should be set to False.\n", - "\n", - "# NB at the moment if you try and create Dome twice it wont work because the gpio pins from the first instance wont be released.\n", - "testdome = Dome(testing=True, debug_lights=False)" + "# set the loggin level\n", + "import logging\n", + "logger = logging.getLogger()\n", + "logger.setLevel(logging.INFO)\n", + "#logger.setLevel(logging.DEBUG)" ] }, { @@ -32,9 +37,16 @@ "metadata": {}, "outputs": [], "source": [ - "# the calibrate method tells the dome to rotate n times (default n=2) and use the encoder counts to determine the degrees of rotation per encoder tick\n", - "# In testing mode, we will simulate 10 ticks per rotation, for 20 ticks total.\n", - "testdome.calibrate_dome_encoder_counts()" + "\"\"\"\n", + "testing=True means all the automationHAT functionality and the state of the GPIOzero pins will be \n", + "mocked/simulated debug_lights=True means the automationHAT status LEDs will be enabled on the \n", + "automationHAT. If you do not have an automationHAT this should be set to False.\n", + "\n", + "NB at the moment if you try and create Dome twice it wont work because the gpio pins from the \n", + "first instance wont be released.\n", + "\"\"\"\n", + "\n", + "testdome = Dome(testing=True, debug_lights=False)" ] }, { @@ -43,8 +55,13 @@ "metadata": {}, "outputs": [], "source": [ - "# We can now check the the degrees per tick factor and the encoder count\n", - "print(f'Dome rotates {testdome.degrees_per_tick} degrees per encoder tick. Current encoder count is {testdome.encoder_count}.')" + "\"\"\"\n", + "The calibrate method tells the dome to rotate n times (default n=2) and use the encoder counts to \n", + "determine the degrees of rotation per encoder tick.\n", + "In testing mode, we will simulate 10 ticks per rotation, for 20 ticks total.\n", + "\"\"\"\n", + "\n", + "testdome.calibrate_dome_encoder_counts()" ] }, { @@ -53,7 +70,11 @@ "metadata": {}, "outputs": [], "source": [ - "print(testdome.encoder_count)" + "\"\"\"\n", + "We can now check the the degrees per tick factor and the encoder count\n", + "\"\"\"\n", + "\n", + "print(f'Dome rotates {testdome.degrees_per_tick} degrees per encoder tick. Current encoder count is {testdome.encoder_count}.')" ] }, { @@ -62,8 +83,12 @@ "metadata": {}, "outputs": [], "source": [ - "# If we are in testing mode, lets now tell it that is at an azimuth of 90 degrees, an encoder count of 9 and that it rotates 10 degrees per tick.\n", - "# ND these are all private member variables, so to access them we need to use the \"name mangled\" member variable names\n", + "\"\"\"\n", + "If we are in testing mode, lets now tell it that is at an azimuth of 90 degrees, an encoder count \n", + "of 9 and that it rotates 10 degrees per tick. ND these are all private member variables, so to \n", + "access them we need to use the \"name mangled\" member variable names\n", + "\"\"\"\n", + "\n", "testdome._encoder_count = 9\n", "testdome._degrees_per_tick = Angle(10 * u.deg)\n", "testdome._dome_az = Angle(90 * u.deg)" @@ -77,7 +102,8 @@ "source": [ "print(testdome.encoder_count)\n", "print(testdome.degrees_per_tick)\n", - "print(testdome.dome_az)" + "print(testdome.dome_az)\n", + "print(testdome.az_position_tolerance)" ] }, { @@ -86,7 +112,8 @@ "metadata": {}, "outputs": [], "source": [ - "# check where the dome thinks it is\n", + "\"\"\"Check where the dome thinks it is\"\"\"\n", + "\n", "testdome.dome_az" ] }, @@ -96,8 +123,11 @@ "metadata": {}, "outputs": [], "source": [ - "# now we can tell it to go to an azimuth of 300 degrees. The dome will realise it is quicker to rotate anticlockwise\n", - "testdome.GotoAz(300)" + "\"\"\"Now we can tell it to go to an azimuth of 300 degrees. The dome will realise it is quicker to rotate anticlockwise\"\"\"\n", + "\n", + "testdome.goto_az(300)\n", + "while testdome.movement_thread_active:\n", + " time.sleep(1)" ] }, { @@ -108,7 +138,8 @@ "source": [ "print(testdome.encoder_count)\n", "print(testdome.degrees_per_tick)\n", - "print(testdome.dome_az)" + "print(testdome.dome_az)\n", + "print(testdome.az_position_tolerance)" ] }, { @@ -117,7 +148,8 @@ "metadata": {}, "outputs": [], "source": [ - "# we can now check if the dome ended up where we wanted.\n", + "\"\"\"We can now check if the dome ended up where we wanted.\"\"\"\n", + "\n", "print(f'Dome is currently at an azimuth of {testdome.dome_az}, with an encoder count of {testdome.encoder_count}')" ] }, @@ -127,20 +159,20 @@ "metadata": {}, "outputs": [], "source": [ - "# currently the dome will overshoot the position depending on how fine the az_per_tick instance variable is (10 degrees is pretty coarse).\n", - "# The dome azimuth is only updated according to how many ticks were recorded, so even if it overshoots it should still know where it is.\n", - "# after every movement, once the dome_az is update the encoder is set to the corresponding number of ticks as if it had just rotated from\n", - "# azimuth of zero to the current location (encoder_count = dome_az/az_per_tick)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# now send the dome to an azimuth of 2 degrees, in this case the dome will decide to rotate clockwise.\n", - "testdome.GotoAz(2)" + "\"\"\"\n", + "Currently the dome will overshoot the position depending on how fine the az_per_tick instance \n", + "variable is (10 degrees is pretty coarse). The dome azimuth is only updated according to how \n", + "many ticks were recorded, so even if it overshoots it should still know where it is. After \n", + "every movement, once the dome_az is update the encoder is set to the corresponding number of \n", + "ticks as if it had just rotated from azimuth of zero to the current location \n", + "(encoder_count = dome_az/az_per_tick)\n", + "\n", + "Now send the dome to an azimuth of 2 degrees, in this case the dome will decide to rotate clockwise.\n", + "\"\"\"\n", + "\n", + "testdome.goto_az(2)\n", + "while testdome.movement_thread_active:\n", + " time.sleep(1)" ] }, { @@ -149,7 +181,8 @@ "metadata": {}, "outputs": [], "source": [ - "# we can now check if the dome ended up where we wanted.\n", + "\"\"\"We can now check if the dome ended up where we wanted.\"\"\"\n", + "\n", "print(f'Dome is currently at an azimuth of {testdome.dome_az}, with an encoder count of {testdome.encoder_count}')" ] }, @@ -177,7 +210,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.7" } }, "nbformat": 4,