#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
#
#  wxDomeTrackerFrame.py
#  
#  Copyright 2010-2014 Matthew Cutone <matthew.cutone@gmail.com>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#  GUI code generated by wxGlade 0.6.3 on Sun Jun 26 00:02:02 2011

# ==============================================================================
#
# APPLICATION IMPORTS
#
# ==============================================================================

# Standard Library Imports

import sys
import os
import time
import urllib2
import sqlite3
import string
import threading
import math
import sysinfo

# Astronomy and Hardware Imports

from obstools import obsmathlib
from obstools import argoterm
from obstools import obslib
import stellarium_server

# DomeTracker Data/Threading Imports

import sysobj
import execf
import cmacro
import socket
import dtproc
import macrof

# GUI Imports

import wx
from wx.lib.pubsub import Publisher
import wx.lib.agw.aui as aui
from wx.lib.wordwrap import wordwrap

from wxdlg import DomeTrackingFrame
from wxdlg import ConnectionsFrame
from wxdlg import PowerFrame
from wxdlg import StarBrowserFrame
from wxdlg import TelescopeModeFrame
from wxdlg import SetTargetObject
from wxdlg import RainSensorFrame
from wxdlg import DigitalSettingCircles
from wxdlg import RecordMacroFrame
from wxdlg import CalibrateArgoFrame
from wxdlg import SessionsCalculatorFrame
from wxdlg import ConfigureServerFrame

from ObsControlPanel import ObsControlPanel
from sysdisp import CoordDisplay

# ==============================================================================
#
# APPLICATION CONSTANTS
#
# ==============================================================================

# File Paths for various resources

INSTALLED_PATH = os.path.abspath('')
IMAGE_DIRECTORY = os.path.join(INSTALLED_PATH, "res")
ICON_DIRECTORY = os.path.join(IMAGE_DIRECTORY, "24")
CONFIG_DIRECTORY = os.path.join(INSTALLED_PATH, "dat")
MACRO_DIRECTORY = os.path.join(INSTALLED_PATH, "macros")
OBJECT_DIRECTORY = os.path.join(INSTALLED_PATH, "objects")
DOC_DIRECTORY = os.path.join(INSTALLED_PATH, "doc")
SQL_DATABASE = os.path.join(CONFIG_DIRECTORY, "objects.dat")
DEFAULT_LOG_FILE = os.path.join(INSTALLED_PATH, "systemlog.txt")
SYSTEM_LOG_PATH = './log/sysout.txt'

# Weather information data

#WEATHER_URL = "http://text.www.weatheroffice.gc.ca"
#WEATHER_PAGE =  "/rss/city/on-143_e.xml"
#WEATHER_UPDATE_INTERVAL = 30000

# Legacy dometracker variables

DEFAULT_REPEAT_INTERVAL = 100
DEFAULT_DOME_PULSE_READ_INTERVAL = 50
DEFAULT_HARDWARE_READOUT_INTERVAL = 150
DEFAULT_SLEEP_TIME = 5.0

SOCKET_HOST = 'localhost'
SOCKET_PORT = 10001

# Parallel data for HWC

NORMALIZE_DATA = 0x00
NORMALIZE_CONTROL = 0x0B
POWER_OFF = 0x80
MOVE_LEFT = 0x20
MOVE_RIGHT = 0x10
DOME_CLOSE = 0x40
DOME_OPEN = 0x09 # Control
DOME_PULSE = 0x80
SENSE_ZERO_AZMTH = 0x20
SENSE_RAIN = 0x40

# Application variables

APP_NAME = "wxDomeTracker"
APP_VERSION = "5.0.1 (6.0 BETA)"

# Interface Event IDs

(ID_SAVE, ID_OPEN, ID_CONNECT, ID_ADD_OBJECT, ID_TARGET, ID_CHART_CCW, 
    ID_CHART_CW, ID_POWER, ID_RAIN_SENSOR, ID_CONN_CONFIG, ID_DOME_TRACKER, 
    ID_SERVER_CONFIG, ID_TELESCOPE, ID_CONTROL, ID_PORTS, ID_SIMBAD, 
    ID_CANCEL, ID_LOG, ID_SETTING_CIRCLES, ID_SHUTDOWN, ID_MACRO_REC, 
    ID_MACRO_PLAY, ID_MACRO_BREAK, ID_SET_FONT, ID_EXIT, 
    ID_END_TRACKING, ID_SESSION_CALC, ID_RUN_ACTION, ID_TEST_MODE, 
    ID_STEP_MACRO, ID_MACRO_RUN, ID_SEND_TARGET, ID_SEND_PARK,
    ID_SET_FONT_COLOUR) = range(300, 334, 1)
    
# ==============================================================================
#
# DOMETRACKER MAIN FRAME CODE AND HELPER CLASSES
#
# ==============================================================================

class ArgoCommunicationThread(threading.Thread):
    def __init__(self, term_addr=0, term_poll=0.25):
        threading.Thread.__init__(self)
        self.term_addr = term_addr
        self.term_poll = term_poll
        self.cmd_queue = None
        self.abort = False
        
        Publisher().subscribe(self.interrupt, "argo_tx")
        
        self.argoConnectionObject = argoterm.ArgoInterface(term_addr)
    
    def abort(self):
        self.abort = True
    
    def interrupt(self, msg):
        self.cmd_queue = msg.data
            
    def run(self):
        while not self.abort:
            if self.cmd_queue == None:
                #try:
                rad = self.argoConnectionObject.getRAD(2)
                wx.CallAfter(Publisher().sendMessage, "argo_comm", rad)
                time.sleep(self.term_poll)
                #except:
                #    continue
            else:
                #try:
                    # no feed back yet, assume the command worked
                recv = self.argoConnectionObject.echoCommand(self.cmd_queue)
                time.sleep(self.term_poll)
                self.cmd_queue = None
                #except:
                #    continue
                
        self.argoConnectionObject.closeConnection()
    
#class WeatherReportThread(threading.Thread):
    #def __init__(self, city="Toronto, Ontario, Canada"):
        #threading.Thread.__init__(self)
        #self.city = city
            
    #def run(self):
        ##create google weather api url
        #url = "http://www.google.com/ig/api?weather=" + urllib2.quote(self.city)

        #try:
            #f = urllib2.urlopen(url)
        #except:
            #return "Error opening url"

        #s = f.read()        
        #weather = s.split("<current_conditions><condition data=\"")[-1].split("\"")[0]
        #temperature = s.split("<temp_c data=\"")[-1].split("\"")[0]

        #if weather == "<?xml version=":
            #print("Invalid city")
        
        ##degree_symbol = unichr(176).encode("latin-1")
        #weather_string = "%s %sC" % (weather, temperature)
        
        #wx.CallAfter(Publisher().sendMessage, "weather", weather_string)
        #time.sleep(120)

class wxDomeTrackerFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: wxDomeTrackerFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        
        self._mgr = aui.AuiManager()
        self._mgr.SetManagedWindow(self)
        
        # create a state object and load interface data
        self.StateObject = sysobj.SystemStateObject()
        #self.StateObject.LoadHWInterfaces(os.path.join(CONFIG_DIRECTORY, "settings.conf"))
        
        # load macro recorder and process manager objects
        self.MacroRecorderObject = cmacro.MacroRecorder(self)
        self.pstack = dtproc.ProcessStack(self)
        
        # bunch of variable not handled by the state object yet
        # keep them here since they work in ther mean time
        self.controlPanel = None
        
        self.logFileDirectory = None
        
        self.currentTarget = None
        self.currentLMST = None
        self.currentConditions = None
        
        self.macro_buffer = []
        self.macro_lock = False
        self.macro_thread = None

        self.shutdownDay = 1
        self.shutdownMonth = 1
        self.shutdownYear = 2011
        self.shutdownHour = 0
        self.shutdownMinute = 0
        self.shutdownThread = None
        self.serialPortAddr = 0
        self.baudrate = 38400
        self.termPoll = 500 # milliseconds
        self.lpt = 'lpt1'
        
        self.enableRainSensor = True
        self.rainSensorPollInterval = 1 # seconds
        self.rainSensorWarning = True
        self.rainSensorShutdown = True
        self.rainSensorDelay = 30000 # milliseconds
        
        self.telescopeMode = 1 # 0 = Argo, 1 = Virtual
        self.telescopeRightAscension = (0,0,0)
        self.telescopeDeclination = (0,0,0)
        
        self.virtualTelescopeCoords = ((0,0,0), (0,0,0))
        self.argoNavisCoords = ((0,0,0), (0,0,0))
        
        self.domeObjectAlt = 0.0
        self.domeObjectAzmth = 0.0
        self.oldDomeObjectAz = 0.0
        self.oldDomeObjectAlt = 0.0
        
        self.domeAzimuth = 0.00
        self.domeAcc = 0.00
        self.domeTrackingMode = 1
        #self.domeTrackingEnabled = False
        self.domeTrackingPollingInterval = 60
        self.domeTrackingThresholdDegrees = 5
        self.domeTrackingStage = 0
        self.domeObjectAirmass = 0.0
        
        self.enableCalibration = False
        self.calibrationObject = None
        self.RAOffset = 0.0
        self.DecOffset = 0.0
                
        self.argoNavisConnected = 0
        self.argoConnectionObject = None
        
        self.testModeEnabled = False
        self.digitalSettingCircles = None
        self.digitalSettingCirclesCount = 0
            
        # load the hardware interface library and point to it
        if wx.Platform == "__WXMSW__":
            from obstools import obslib_linux
            self.hwc = obslib.ApparatusInterface('lpt1')
            #print self.hwc.getRainPulse()
        else:
            from obstools import obslib_linux
            # dummy module for the linux port, just simulates a interface
            # there is no linux interface, just used for running
            # from delphinus
            self.hwc = obslib_linux.ApparatusInterface('lpt1')
        
        # reset the HWC control ranges 
        self.hwc.resetControlRange()
        self.hwc.resetDataRange()
        
        # macro vars
        self.macro_data = None
        self.event_type = None
        self.event_tscale = None
        self.event_tzero = None
        self.macro_step = 1
        
        # path of the log file, just default this when opening
        self.logFilePath = DEFAULT_LOG_FILE
        
        # ==============================================================
        # LOAD GUI ELEMENTS
        # ==============================================================
        
        # load the menu bar
        self.mbrMain = wx.MenuBar()
        self.mnuFile = wx.Menu()
        #self.mnuLoadConfig = wx.MenuItem(self.mnuFile, wx.ID_OPEN, 
            #"&Load Configuration\tCtrl+O", "", wx.ITEM_NORMAL)
        #self.mnuFile.AppendItem(self.mnuLoadConfig)
        #self.mnuSaveConfig = wx.MenuItem(self.mnuFile, wx.ID_SAVE, 
            #"&Save Configuration\tCtrl+S", "", wx.ITEM_NORMAL)
        #self.mnuFile.AppendItem(self.mnuSaveConfig)
        #self.mnuFile.AppendSeparator()
        self.mnuDumpDebug = wx.MenuItem(self.mnuFile, 9999, 
            "&Dump Debug Information", "", wx.ITEM_NORMAL)
        self.mnuFile.AppendItem(self.mnuDumpDebug)
        self.mnuFile.AppendSeparator()
        self.mnuExit = wx.MenuItem(self.mnuFile, ID_EXIT, 
            "Exit\tCtrl+E", "", wx.ITEM_NORMAL)
        self.mnuFile.AppendItem(self.mnuExit)
        self.mbrMain.Append(self.mnuFile, "&File")
        self.mnuConfigure = wx.Menu()
        self.mnuConnectInput = wx.MenuItem(self.mnuConfigure, 202, 
            "Connect &Argo Navis\tF2", "", wx.ITEM_CHECK)
        self.mnuConfigure.AppendItem(self.mnuConnectInput)
        self.mnuConfigure.AppendSeparator()
        self.mnuServer = wx.MenuItem(self.mnuConfigure, ID_SERVER_CONFIG, 
            "&Planetarium Server", "", wx.ITEM_NORMAL)
        self.mnuConfigure.AppendItem(self.mnuServer)
        self.mnuConnections = wx.MenuItem(self.mnuConfigure, ID_PORTS, 
            "Setup &Connections\tF3", "", wx.ITEM_NORMAL)
        self.mnuConfigure.AppendItem(self.mnuConnections)
        self.mnuConfigure.AppendSeparator()
        self.mnuLogging = wx.MenuItem(self.mnuConfigure, ID_LOG, 
            "&Log Write Path", "", wx.ITEM_NORMAL)
        self.mnuConfigure.AppendItem(self.mnuLogging)
        self.mnuLogFont = wx.MenuItem(self.mnuConfigure, ID_SET_FONT, 
            "Output Display &Font Size", "", wx.ITEM_NORMAL)
        self.mnuConfigure.AppendItem(self.mnuLogFont)
        self.mnuConfigure.AppendSeparator()
        self.mnuRainSensor = wx.MenuItem(self.mnuConfigure, ID_RAIN_SENSOR, 
            "&Rain Sensor", "", wx.ITEM_NORMAL)
        self.mnuConfigure.AppendItem(self.mnuRainSensor)
        self.mbrMain.Append(self.mnuConfigure, "&Settings")
        self.mnuDisplay = wx.Menu()
        self.mnuShowSettingCircles = wx.MenuItem(self.mnuDisplay, ID_SETTING_CIRCLES, 
            "Setting &Circles\tF4", "", wx.ITEM_NORMAL)
        self.mnuDisplay.AppendItem(self.mnuShowSettingCircles)
        self.mbrMain.Append(self.mnuDisplay, "&Display")
        self.mnuControls = wx.Menu()
        self.mnuSetTarget = wx.MenuItem(self.mnuControls, ID_TARGET, 
            "Set &Target\tCtrl+T", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuSetTarget)
        self.mnuControls.AppendSeparator()
        self.mnuSendTargetArgo = wx.MenuItem(self.mnuControls, ID_SEND_TARGET, 
            "Send Target Data to Argo NAVIS", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuSendTargetArgo)
        self.mnuSendParkDataArgo = wx.MenuItem(self.mnuControls, ID_SEND_PARK, 
            "Update Argo NAVIS 'Home' Position", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuSendParkDataArgo)
        self.mnuControls.AppendSeparator()
        self.mnuTelescope = wx.MenuItem(self.mnuControls, ID_TELESCOPE, 
            "Tele&scope Position\tCtrl+P", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuTelescope)
        #self.mnuCalibrateArgo = wx.MenuItem(self.mnuControls, wx.NewId(), 
        #    "&Calibrate Argo Navis", "", wx.ITEM_NORMAL)
        #self.mnuControls.AppendItem(self.mnuCalibrateArgo)
        self.mnuControls.AppendSeparator()
        self.mnuControlPanel = wx.MenuItem(self.mnuControls, ID_CONTROL, 
            "&Control Panel\tF5", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuControlPanel)
        self.mnuControls.AppendSeparator()
        self.mnuPower = wx.MenuItem(self.mnuControls, ID_POWER, 
            "&Power Managment\tF6", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuPower)
        self.mnuShutdown = wx.MenuItem(self.mnuControls, ID_SHUTDOWN, 
            "&Complete Shutdown", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuShutdown)
        self.mnuControls.AppendSeparator()
        self.mnuCancel = wx.MenuItem(self.mnuControls, ID_CANCEL, 
            "&Cancel Action", "", wx.ITEM_NORMAL)
        self.mnuControls.AppendItem(self.mnuCancel)
        self.mbrMain.Append(self.mnuControls, "&Controls")
        self.mnuMacros = wx.Menu()
        self.mnuRecordMacro = wx.MenuItem(self.mnuMacros, ID_MACRO_REC, 
            "Record &New Macro...\tCtrl+R", "", wx.ITEM_NORMAL)
        self.mnuMacros.AppendItem(self.mnuRecordMacro)
        self.mnuMacros.AppendSeparator()
        self.mnuLoadMacro = wx.MenuItem(self.mnuMacros, ID_MACRO_PLAY, 
            "&Load Macro File...", "", wx.ITEM_NORMAL)
        self.mnuMacros.AppendItem(self.mnuLoadMacro)
        self.mnuMacros.AppendSeparator()
        self.mnuRunMacro = wx.MenuItem(self.mnuMacros, ID_MACRO_RUN, 
            "&Run Macro", "", wx.ITEM_NORMAL)
        self.mnuMacros.AppendItem(self.mnuRunMacro)
        self.mnuStepMacro = wx.MenuItem(self.mnuMacros, ID_STEP_MACRO, 
            "&Step Into Next Action", "", wx.ITEM_NORMAL)
        self.mnuMacros.AppendItem(self.mnuStepMacro)
        self.mnuStopMacro = wx.MenuItem(self.mnuMacros, ID_MACRO_BREAK, 
            "&Break/Stop Macro", "", wx.ITEM_NORMAL)
        self.mnuMacros.AppendItem(self.mnuStopMacro)
        self.mnuMacros.AppendSeparator()
        self.mnuTestMode = wx.MenuItem(self.mnuMacros, ID_TEST_MODE, 
            "&Test Mode (Telescope Relative)", "", wx.ITEM_CHECK)
        self.mnuMacros.AppendItem(self.mnuTestMode)
        self.mbrMain.Append(self.mnuMacros, "&Macros")
        self.mnuTools = wx.Menu()
        self.mnuSessionCalculator = wx.MenuItem(self.mnuTools, ID_SESSION_CALC, 
            "Research Session Calculator", "", wx.ITEM_NORMAL)
        self.mnuTools.AppendItem(self.mnuSessionCalculator)
        self.mbrMain.Append(self.mnuTools, "&Tools")
        self.mnuHelp = wx.Menu()
        self.mnuAbout = wx.MenuItem(self.mnuHelp, wx.ID_ABOUT, 
            "&About wxDomeTracker", "", wx.ITEM_NORMAL)
        self.mnuHelp.AppendItem(self.mnuAbout)
        self.mbrMain.Append(self.mnuHelp, "&Help")
        self.SetMenuBar(self.mbrMain)
        self.stbMain = self.CreateStatusBar(4, wx.ST_SIZEGRIP)
        
        # load the toolbar
        self.frmMain_toolbar = wx.ToolBar(self, -1, style=wx.TB_HORIZONTAL | 
            wx.TB_FLAT|wx.TB_TEXT | 
            wx.TB_NODIVIDER | 
            wx.TB_HORZ_TEXT)
        
        # load toolbar items
        #self.frmMain_toolbar.AddLabelTool(wx.NewId(), "Open", 
            #wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "document-open.png")), 
            #shortHelp="Load configuration profile from file", longHelp="Load")
        #self.frmMain_toolbar.AddLabelTool(wx.NewId(), "Save", 
            #wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "document-save.png")), 
            #shortHelp="Save current profile configuration to file", longHelp="Save")
        #self.frmMain_toolbar.AddSeparator()
        self.frmMain_toolbar.AddLabelTool(ID_PORTS, "Ports", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "stock_ports.png")), 
            shortHelp="Configure hardware connections", longHelp="Ports")
        self.frmMain_toolbar.AddSeparator()
        self.frmMain_toolbar.AddLabelTool(ID_TARGET, "Target", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "stock_hyperlink-target.png")), 
            shortHelp="Set system target", longHelp="Target")
        self.frmMain_toolbar.AddLabelTool(ID_CONTROL, "Control", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "stock_styles.png")), 
            shortHelp="Show control panel", longHelp="Control")
        self.frmMain_toolbar.AddLabelTool(ID_POWER, "Power", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "gnome-shutdown.png")), 
            shortHelp="Show power managment settings", longHelp="Power")
        self.frmMain_toolbar.AddSeparator()
        self.frmMain_toolbar.AddLabelTool(ID_CANCEL, "Cancel", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "process-stop.png")), 
            shortHelp="Cancel active control process", longHelp="Cancel")
        self.frmMain_toolbar.AddSeparator()
        self.frmMain_toolbar.AddLabelTool(ID_MACRO_PLAY, "Macro", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "stock_script.png")), 
            shortHelp="Load control macro from file", longHelp="Load Macro")
        self.frmMain_toolbar.AddLabelTool(ID_MACRO_RUN, "Run", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "media-playback-start.png")), 
            shortHelp="Run loaded macro", longHelp="Run macro")
        self.frmMain_toolbar.AddLabelTool(ID_MACRO_BREAK, "Stop", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "media-playback-stop.png")), 
            shortHelp="Stop currently running macro", longHelp="Stop")
        self.frmMain_toolbar.AddSeparator()
        self.frmMain_toolbar.AddLabelTool(ID_EXIT, "Exit", 
            wx.Bitmap(os.path.join(IMAGE_DIRECTORY, "application-exit.png")), 
            shortHelp="Exit wxDomeTracker", longHelp="Exit")

        self.SetToolBar(self.frmMain_toolbar)
        
        # load information panel objects
        self.INFODISPLAY = sysinfo.CoordDisplay(self)
        
        # register thread communication routes
        Publisher().subscribe(self.writeFromThread, "display")
        Publisher().subscribe(self.releaseMacroLock, "macro")
        Publisher().subscribe(self.setDomeAction, "update")
        Publisher().subscribe(self.shutdownCompleted, "shutdown")
        #Publisher().subscribe(self.setWeatherStatus, "weather")
        Publisher().subscribe(self.serverStatus, "display_server")
        Publisher().subscribe(self.updateArgoCoords, "argo_comm")
        
        # do the layout and set frame properties
        self.__set_properties()
        self.__do_layout()
        
        # bind all GUI events to something
        #self.Bind(wx.EVT_MENU, self.OnLoadConfig, self.mnuLoadConfig)
        #self.Bind(wx.EVT_MENU, self.OnSaveConfig, self.mnuSaveConfig)
        self.Bind(wx.EVT_MENU, self.OnClose, self.mnuExit)
        self.Bind(wx.EVT_MENU, self.OnConnectInput, self.mnuConnectInput)
        self.Bind(wx.EVT_MENU, self.OnServerSettings, self.mnuServer)
        self.Bind(wx.EVT_MENU, self.OnSetupConnections, self.mnuConnections)
        #self.Bind(wx.EVT_MENU, self.OnHardwareInterface, self.mnuHardwareInterface)
        self.Bind(wx.EVT_MENU, self.OnLogging, self.mnuLogging)
        self.Bind(wx.EVT_MENU, self.OnLogFont, self.mnuLogFont)
        self.Bind(wx.EVT_MENU, self.OnRainSensor, self.mnuRainSensor)
        self.Bind(wx.EVT_MENU, self.OnShowSettingCircles, self.mnuShowSettingCircles)
        self.Bind(wx.EVT_MENU, self.OnTelescopePosition, self.mnuTelescope)
        self.Bind(wx.EVT_MENU, self.OnSetTarget, self.mnuSetTarget)
        self.Bind(wx.EVT_MENU, self.OnSendTarget, self.mnuSendTargetArgo)
        self.Bind(wx.EVT_MENU, self.OnSendParkData, self.mnuSendParkDataArgo)
        #self.Bind(wx.EVT_MENU, self.OnCalibrateArgo, self.mnuCalibrateArgo)
        self.Bind(wx.EVT_MENU, self.OnControlPanel, self.mnuControlPanel)
        self.Bind(wx.EVT_MENU, self.OnPower, self.mnuPower)
        self.Bind(wx.EVT_MENU, self.OnShutdown, self.mnuShutdown)
        self.Bind(wx.EVT_MENU, self.OnMacroRecord, self.mnuRecordMacro)
        self.Bind(wx.EVT_MENU, self.loadMacro, self.mnuLoadMacro)
        self.Bind(wx.EVT_MENU, self.breakMacro, self.mnuStopMacro)
        self.Bind(wx.EVT_MENU, self.OnTestMode, self.mnuTestMode)
        self.Bind(wx.EVT_MENU, self.stepMacro, self.mnuStepMacro)
        self.Bind(wx.EVT_MENU, self.runMacro, self.mnuRunMacro)
        self.Bind(wx.EVT_MENU, self.stepMacro, self.mnuStepMacro)
        self.Bind(wx.EVT_MENU, self.cancelAction, self.mnuCancel)
        self.Bind(wx.EVT_MENU, self.OnSessionCalculator, self.mnuSessionCalculator)
        self.Bind(wx.EVT_MENU, self.OnAbout, self.mnuAbout)
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        
        # state timer
        self.tmrUpdateState = wx.Timer(self)
        
        # interface timers
        self.tmrUpdateAll = wx.Timer(self)
        #self.tmrGetWeather = wx.Timer(self)
        self.tmrGetPosition = wx.Timer(self)
        self.tmrDomeTrackerPoll = wx.Timer(self)
        self.tmrProcessExec = wx.Timer(self)
        self.tmrShowWarning = wx.Timer(self)

        # server timer
        #self.tmrServer = wx.Timer(self)

        # TIMER EVENTS
        self.Bind(wx.EVT_TIMER, self.updateInformationDisplay, self.tmrUpdateAll)
        self.Bind(wx.EVT_TIMER, self.cycleSystemTime, self.tmrProcessExec)
        #self.Bind(wx.EVT_TIMER, self.serverDataUpdate, self.tmrServer)
        self.Bind(wx.EVT_TIMER, self.domeTrackingPollEvent, self.tmrDomeTrackerPoll)
        self.Bind(wx.EVT_TIMER, self.ShowWarning, self.tmrShowWarning)
        
        # ENABLE GLOBAL TIMERS
        self.tmrUpdateAll.Start(120)
        self.tmrProcessExec.Start(250)
        
        self.old_time = None
        
        # welcome messages
        msg = "%s YUO 60cm Telescope Control Console - Version %s" % (APP_NAME, APP_VERSION)
        self.writeOutput(msg, False)
        msg = "Copyright 2008 - 2015 Matthew Cutone, Jesse Rogerson and Richard Bloch"
        self.writeOutput(msg, False)
        self.writeOutput(' ', False)
        
        if self.StateObject.server_vars["SERVER_ON_START"]:
            self.extsrv = stellarium_server.StellariumInterface(
                self.StateObject.server_vars["SERVER_HOST_ADDR"], 
                self.StateObject.server_vars["SERVER_PORT"])
            self.StateObject.server_vars["SERVER_ACTIVE"] = True
            #self.tmrServer.Start(250)
            msg = "Stellarium TCP server started at host '%s:%s'." % (
                self.StateObject.server_vars["SERVER_HOST_ADDR"], 
                self.StateObject.server_vars["SERVER_PORT"])
            self.writeOutput(msg, True)
        
        # load a control panel object
        self.controlPanel = ObsControlPanel(self)
        #self.controlPanel.Show()
        
        # load weather reporter
        #self.weatherThread = WeatherReportThread()
        #self.weatherThread.setDaemon(True)
        #self.weatherThread.start()
        self._mgr.Update() # set the AUI layout
        
        # check if the rain sensor needs to be reset before doing anything
        self.INFODISPLAY.SetRainSensorText('Updating...')
        if self.hwc.getRainPulse():
            self.INFODISPLAY.SetRainSensorText('Check Sensor')
            dlg = wx.MessageDialog(self, 
                "DomeTracker has detected the rain sensor is active. Please reset it before starting a research session.", 
                'Rain Sensor Warning', wx.OK | wx.ICON_WARNING)
                
            dlg.ShowModal()

        self.tmrShowWarning.Start(20)

    def __set_properties(self):
        # begin wxGlade: wxDomeTrackerFrame.__set_properties
        self.SetTitle("%s V%s - York Univeristy Observatory" % (APP_NAME, APP_VERSION))
        self.SetSize((1200, 750))
        self.SetMinSize((500,500))
        self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE))
        self.stbMain.SetStatusWidths([150, 150, 150, -1])
        # statusbar fields
        stbMain_fields = ["--", "DISCONNECTED", "Manual Shutdown", "No macro loaded"]
        for i in range(len(stbMain_fields)):
            self.stbMain.SetStatusText(stbMain_fields[i], i)
        self.INFODISPLAY.SetArgoConnectionStatus('DISCONNECTED')

    def __do_layout(self):
        # realize the toolbar
        self.frmMain_toolbar.SetToolBitmapSize((24,24))
        self.frmMain_toolbar.Realize()
        
        self._mgr.AddPane(self.INFODISPLAY, aui.AuiPaneInfo().Name("log_pnl").Caption("Information Panel").
                          CentrePane().Layer(1).Show().MaximizeButton(False).MinimizeButton(False).Floatable(True).
                          CloseButton(False).TopDockable(False).BottomDockable(False).FloatingSize((305, 500)).
                          MinSize((305, 500)))
        
        # position on screen
        self.Layout()
        self.Centre()
    
    def ShowWarning(self, evt=None):
        # ask if the user wants to set the agro for telescope telemetry
        self.tmrShowWarning.Stop()

        msg = "WARNING! Check if the 'Telescope & Dome Control' switch is ON before continuing."
        self.writeOutput(msg, timestamp=True)
        self.writeOutput(' ', False)

        dlg = wx.MessageDialog(None, "WARNING! Check if the 'Telescope & Dome Control' switch is ON before continuing. Click OK if ON.", 'PC Control', 
            wx.OK | wx.ICON_WARNING)
        
        dlg.ShowModal()
        
        if evt: evt.Skip()
        
# ==============================================================================
# DYNAMIC WIDGETS (AUI)
# ==============================================================================

#   Here is where the code that generates panes that are managed by the AUI
#   manager.
#
#   MDC

    def CreateInformationPanel(self, parent):
        pnlSideBar = wx.ScrolledWindow(parent, -1) # style=wx.TAB_TRAVERSAL|wx.RAISED_BORDER)
        

# ==============================================================================
# STATUS INDICATORS
# ==============================================================================
#
#   These functions are use to set the state of the status indicators. Thre is a 
#   bit of a hack to make this work. The controls 'shrink wrap' text everytime 
#   you set the label. So you need to get the initial size and then set it after
#   the label changes.
#
#   MDC

    def setArgoConnectionStatus(self, state=True):
        pass

    def setTrackingStatus(self, state=True):
        pass
            
    def setShutdownStatus(self, state=True):
        pass
            
    def setSystemStatus(self, state):
        pass

        
# ==============================================================================
# MACRO FUNCTIONS
# ==============================================================================
#
#   These functions are responsible for recording/playback of macros. At the 
#   moment these functions reside in this file. They _may_ be moved to another
#   file when the functions get much more complex.
#
#   Simply a macro program contains user input patterns. These are saved and 
#   played back at a later time. A macro entry is generated when an event
#   starts (i.e dome rotation). Its start time and duration are recorded so it 
#   can be repeated at a later time when the macro is "played" back.
#  
#   Since macro events take place at any time, it depends on a much higher time
#   resolution than the rest of the system. So fractional seconds are computed 
#   and added to the system time.
#
#   This system is really crude but does work at the moment. Should be seeing a
#   rewrite at some time. 
#
#   MDC
#

    def releaseMacroLock(self, msg):
        self.macro_thread = None
        self.setSystemStatus(False)
        self.unlockMacro()

    def unlockMacro(self):
        self.macro_lock = False
    
    def lockMacro(self):
        self.macro_lock = True
    
    def checkMacroLock(self):
        return self.macro_lock
    
    def OnMacroRecord(self, event):
        #if self.StateObject.macros_vars["MACRO_RUNNING"]:
        if self.pstack.ProcessTypeRunning('MACRO_RUNNING'):
            dlg = wx.MessageDialog(self, 
                "Macro running! Stop the running macro before attempting to record a new one.", 
                'Macro Error', wx.OK | wx.ICON_ERROR)
                
            dlg.ShowModal()
            
            return
        
        #if not self.StateObject.macros_vars["MACRO_RECORDING"]:
        if not self.pstack.ProcessTypeRunning('MACRO_RECORDING'):
            file_name = self.logFilePath
            dlg = RecordMacroFrame(self)
            
            if dlg.ShowModal() == wx.ID_OK:
                macro_settings = dlg.GetValues()
                # check if the macro in HA mode has a target to check against
                if not (macro_settings[1] == 1 and not self.StateObject.isTarget()):
                    self.StateObject.macros_vars["MACRO_NAME"] = macro_settings[0]
                    self.StateObject.macros_vars["MACRO_SCALE"] = macro_settings[1]
                    self.StateObject.macros_vars["MACRO_FLAGS"] = macro_settings[2]
                    
                    # set this as a process, priority 0
                    self.pstack.PushProcess(0, 'MACRO_RECORDING', 'Recording a new macro', 1, None)
                                
                    # warning message for macro recording
                    time_scale_strings = {0 : 'SIDERIAL TIME', 1 : 'HOUR ANGLE'}
                    msg = "Recording control macro '%s' using %s." % (macro_settings[0], 
                        time_scale_strings[macro_settings[1]])
                    self.writeOutput(msg, timestamp=True)
                    msg = "Interaction with the Observatory Control Panel is being recorded. " \
                        "Avoid using external controls unless required."
                    dlg = wx.MessageDialog(self, msg, 'Warning', wx.OK | wx.ICON_WARNING)
                    dlg.ShowModal()

                    stb_text = "RECORDING macro '{0}'".format(macro_settings[0])
                    self.stbMain.SetStatusText(stb_text, 3)
                else:
                    msg = "Cannot record macro with mode HOUR ANGLE. No target object specified."
                    dlg = wx.MessageDialog(self, msg, 'Macro Error', wx.OK | wx.ICON_ERROR)
                    dlg.ShowModal()
                
            dlg.Destroy()
    
    # runs the next macro action in the buffer
    def stepMacro(self, event=None):
        macro_tscale = self.StateObject.macros_vars['MACRO_SCALE']
        if len(self.macro_buffer) != 0:
            if not (self.pstack.ProcessTypeRunning('MACRO_RUNNING') and self.checkMacroLock()):
                c_step = self.macro_buffer[self.macro_step]
                
                hour = c_step['hour']
                minute = c_step['minute']
                second = c_step['second']
                
                a_time = (hour, minute, second)
                
                action = c_step['action']
                duration = c_step['duration']
                
                self.lockMacro()
                #self.stbMain.SetStatusText("MACRO ACTION", 1) 
                # +++                   
                self.macro_thread = execf.ThreadedHardwareMacroAction(self.controlPanel.dc_qin, action, duration)
                self.macro_thread.start()
                self.macro_step += 1
                msg = "Running macro action {0} for duration of {1} seconds.".format(action, duration)
                self.writeOutput(msg)
        else:
            msg = "Cannot step macro, no macro loaded."
            dlg = wx.MessageDialog(self, msg, 'Macro Stepping Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

    def resetRecorderVars(self):
        self.event_type = None
        self.event_tscale = None
        self.event_tzero = None
    
    def startEventRecorder(self, event_type):
        is_recording = self.StateObject.macros_vars["MACRO_EVENT_RECORD"]
        if not is_recording:
            self.StateObject.macros_vars["MACRO_EVENT_RECORD"] = True
            macro_scale = self.StateObject.macros_vars["MACRO_SCALE"] 
            if macro_scale == 0:
                self.event_tscale = self.StateObject.time_vars["CURRENT_LMST"]
            elif macro_scale == 1:
                if not self.testModeEnabled:
                    target_ha = self.StateObject.target_vars["TARGET_HA"]
                else:
                    # the resolution for this is poor, may be problematic if you don't know what you are doing
                    target_ha = self.StateObject.telescope_vars["TELESCOPE_HA"]
                        
                self.event_tscale = (int(target_ha[0]), int(target_ha[1]), int(target_ha[2]))
            
            self.event_tzero = time.time()
            self.event_type = event_type
        else:
            print("MacroRecorder: Already recording, use stopEventRecorder()")
    
    def stopEventRecorder(self):
        if self.controlPanel and self.pstack.ProcessTypeRunning('MACRO_RECORDING'):
            #self.stbMain.SetStatusText("Input Lockout", 1)
            self.controlPanel.lockDomeMotion(1)
            self.controlPanel.lockDomeShutter(1)
            self.setSystemStatus("Input Lockout")
            
        is_recording = self.StateObject.macros_vars["MACRO_EVENT_RECORD"]
        if is_recording:
            duration = "%.1f" % (time.time() - self.event_tzero)
            self.macro_buffer.append((self.event_type, self.event_tscale, duration))
            self.resetRecorderVars()
            self.StateObject.macros_vars["MACRO_EVENT_RECORD"] = False
            
        else:
            print("MacroRecorder: Not recording, use startEventRecorder()")
    
    #
    # Get a list of actions that cannont be executed in the current schedule
    #
    
    def checkMacroSchedule(self):
        past_actions = None
        return past_actions

    def breakMacro(self, evt):
        if self.pstack.ProcessTypeRunning('MACRO_RECORDING'):
            dlg = wx.MessageDialog(None, 'Stop recording macro? Clicking \'yes\' will prompt to save the macro to file.', 'Question', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
            
            if dlg.ShowModal() == wx. ID_YES:
                file_name = self.StateObject.macros_vars["MACRO_NAME"]
                dlg = wx.FileDialog(
                    self, message="Save macro as...", defaultDir=MACRO_DIRECTORY, 
                    defaultFile=file_name, wildcard="Dome Tracker Macro (*.dtm)|*.dtm", style=wx.SAVE|wx.CHANGE_DIR
                    )
                
                if dlg.ShowModal() == wx.ID_OK:
                    path = dlg.GetPath()
                    if not (path.lower()).endswith('.dtm'):
                        path = path + '.dtm'
                        
                    self.saveMacro(path)
                
                dlg.Destroy()
                
                self.macro_buffer = [] # reset buffer
                self.StateObject.macros_vars = {"MACRO_NAME" : None,
                   #"MACRO_RECORDING"        : False,
                   "MACRO_EVENT_RECORD"     : False,
                   #"MACRO_RUNNING"          : False,
                   "MACRO_FLAGS"            : (False, False, False, False), # dome motion, telescope motion, power, focus
                   "MACRO_SCALE"       : 0, # 0 = siderial, 1 = local time
                   "MACRO_FILE_PATH"        : None
                  }
                
                self.pstack.PopProcessType('MACRO_RECORDING')
                #self.pstack.PopProcessType('MACRO_RUNNING')

                msg = " control macro '%s' using %s." % (macro_settings[0], 
                    time_scale_strings[macro_settings[1]])
                self.writeOutput(msg, timestamp=True)
                
                stb_text = "No macro loaded"
                self.stbMain.SetStatusText(stb_text, 3)
                  
        #elif self.StateObject.macros_vars["MACRO_RUNNING"]:
        elif self.pstack.ProcessTypeRunning('MACRO_RUNNING'):
            dlg = wx.MessageDialog(None, 'Stop macro playback?', 'Question', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
                
            path = self.StateObject.macros_vars["MACRO_FILE_PATH"]
            
            if dlg.ShowModal() == wx. ID_YES:
                self.StateObject.macros_vars = {"MACRO_NAME" : None,
                   #"MACRO_RECORDING"        : False,
                   "MACRO_EVENT_RECORD"     : False,
                   #"MACRO_RUNNING"          : False,
                   "MACRO_FLAGS"            : (False, False, False, False),
                   "MACRO_SCALE"       : 0,
                   "MACRO_FILE_PATH"        : None
                  }
                
                #self.pstack.PopProcessType('MACRO_RECORDING')
                self.pstack.PopProcessType('MACRO_RUNNING')
                
                msg = "Execution of macro '%s' TERMINATED." % (os.path.basename(path))
                self.writeOutput(msg, timestamp=True)
                self.macro_buffer = []
                stb_text = "No macro loaded"
                self.stbMain.SetStatusText(stb_text, 3)

    def saveMacro(self, file_name):
        # save macro to a file
        f = open(file_name, 'w')
        f.write("# dome tracker macro, DO NOT EDIT THIS FILE!\n")
        f.write("NAME=%s\n" % self.StateObject.macros_vars["MACRO_NAME"])
        time_scale_string = {0 : "SIDERIAL", 1 : "HOUR_ANGLE"}
        time_sc = self.StateObject.macros_vars["MACRO_SCALE"]
        f.write("MACRO_SCALE=%s\n" % time_scale_string[time_sc])
        
        for action in self.macro_buffer:
            type, time, duration = action
            action_line = "$%s,%s,%s:%s,%s\n" % (time[0], time[1], 
                time[2], type, duration)
            f.write(action_line)
        
        f.close

        msg = "Macro has been written to file '%s'." % os.path.basename(file_name)
        self.writeOutput(msg, timestamp=True)
    
    def loadMacro(self, evt):
        #if self.StateObject.macros_vars["MACRO_RECORDING"]:
        if self.pstack.ProcessTypeRunning('MACRO_RECORDING'):
            dlg = wx.MessageDialog(self, 
                "Macro recording! Stop recording before attempting to load a macro from file.", 
                'Macro Error', wx.OK | wx.ICON_ERROR)
                
            dlg.ShowModal()
            
            return
        
        dlg = wx.FileDialog(
            self, message="Load macro", defaultDir=MACRO_DIRECTORY, 
            defaultFile="", wildcard="Dome Tracker Macro (*.dtm)|*.dtm", style=wx.OPEN|wx.CHANGE_DIR
            )
        dlg.Center()
        
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if not (path.lower()).endswith('.dtm'):
                path = path + '.dtm'
            
            # this variable contains macro data from file
            macro_data = macrof.readMacro(path)
            
            m_scale = macro_data['mode']
            macro_name = macro_data['name']
            
            if (m_scale == 1) and not self.StateObject.isTarget():
                self.macro_buffer == []
                dlg = wx.MessageDialog(self, 
                "Macro '{0}' uses HOUR ANGLE as a reference. It requires a system target to be specified.".format(macro_name), 
                'Macro Error', wx.OK | wx.ICON_ERROR)
                    
                dlg.ShowModal()
            else:
                self.StateObject.macros_vars["MACRO_FILE_PATH"] = path
                self.StateObject.macros_vars["MACRO_SCALE"] = m_scale
                self.StateObject.macros_vars["MACRO_NAME"] = macro_name
                self.macro_buffer = macro_data['exec']
                if len(self.macro_buffer.keys()) > 0:
                    msg = "Macro '{0}' LOADED, click 'Run' to execute the macro.".format(os.path.basename(path))
                    self.writeOutput(msg)
                    stb_text = "LOADED macro '{0}' from file.".format(os.path.basename(path))
                    self.stbMain.SetStatusText(stb_text, 3)
                    
                    dlg = wx.MessageDialog(None, 'The macro has been loaded from file but is not running. Would you like to run it now?', 
                        'Run macro?', 
                        wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
                    
                    if dlg.ShowModal() == wx. ID_YES:
                        self.runMacro()
                else:
                    # macro file has no actions that can be loaded, it will reset itself
                    msg = "Macro file contains no actions."
                    dlg = wx.MessageDialog(self, msg, 'Macro Load Error', wx.OK | wx.ICON_ERROR)
                    dlg.ShowModal()
                
            #print self.macro_buffer
    
    def runMacro(self, evt=None):
        if len(self.macro_buffer) != 0:
            self.pstack.PushProcess(0, 'MACRO_RUNNING', 'Running macro file', 1, None)
            path = self.StateObject.macros_vars["MACRO_FILE_PATH"]
            msg = "Macro '{0}' has been started.".format(os.path.basename(path))
            self.writeOutput(msg)
            stb_text = "RUNNING macro '{0}'".format(os.path.basename(path))
            self.stbMain.SetStatusText(stb_text, 3)
        else:
            msg = "Cannot run macro. Macro must be loaded in order to run."
            dlg = wx.MessageDialog(self, msg, 'Macro Run Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
    
    def processMacroStep(self):
        macro_tscale = self.StateObject.macros_vars['MACRO_SCALE']
        if (type(self.macro_buffer) == dict) and not self.checkMacroLock():
            exec_macro = False
            if self.macro_step <= len(self.macro_buffer.keys()):
                try:
                    c_step = self.macro_buffer[self.macro_step]
                    
                    hour = c_step['hour']
                    minute = c_step['minute']
                    second = c_step['second']
                    
                    a_time = (hour, minute, second)
                    
                    action = c_step['action']
                    duration = c_step['duration']
                    
                    # execute marco using siderial time
                    if (macro_tscale == 0) and (a_time == c_lmst): 
                        exec_macro = True
                    
                    # execute macro using hour angle
                    elif (macro_tscale == 1):
                        # use hour angle
                        if not self.testModeEnabled:
                            target_ha = self.StateObject.target_vars["TARGET_HA"]
                        else:
                            # the resolution for this is poor, may be problematic if you don't know what you are doing
                            target_ha = self.StateObject.telescope_vars["TELESCOPE_HA"]
                            
                        # AUTO-STEP
                        # look ahead an action, is the time less than the current?
                          
                        rnd_ha = (int(target_ha[0]), int(target_ha[1]), int(target_ha[2]))
                        
                        # convert to floats for comparison
                        f_targ_ha = obsmathlib.RightAscensionToHours(int(target_ha[0]), 
                            int(target_ha[1]), int(target_ha[2]))
                        f_time = obsmathlib.RightAscensionToHours(int(hour), int(minute), int(second))
                        
                        if f_targ_ha > f_time:
                            self.stepMacro()
                        elif (a_time == rnd_ha):
                            exec_macro = True
                    
                    # if confirmed, run the macro action
                    if exec_macro:
                        self.lockMacro()
                        #self.stbMain.SetStatusText("MACRO ACTION", 1)                     
                        self.macro_thread = execf.ThreadedHardwareMacroAction(self.controlPanel.dc_qin, action, duration)
                        self.macro_thread.start()
                        self.macro_step += 1
                        msg = "Running macro action {0} for duration of {1} seconds.".format(action, duration)
                        self.writeOutput(msg)
                    
                except KeyError:
                    pass

            else:
                self.StateObject.macros_vars = {"MACRO_NAME" : None,
                   "MACRO_EVENT_RECORD"     : False,
                   "MACRO_FLAGS"            : (False, False, False, False),
                   "MACRO_SCALE"            : 0,
                   "MACRO_FILE_PATH"        : None
                  }
                
                self.pstack.PopProcessType('MACRO_RECORDING')
                self.pstack.PopProcessType('MACRO_RUNNING')
                
                self.macro_buffer = []
                self.macro_step = 1
                msg = "No macro events remaining in queue, macro execution stopped."
                self.writeOutput(msg, timestamp=True)
                stb_text = "No macro loaded"
                self.stbMain.SetStatusText(stb_text, 3)

    def cycleSystemTime(self, evt):
        # global system time is updated only here
        self.StateObject.updateSystemTime()
        c_lmst = self.StateObject.time_vars["CURRENT_LMST"]
        #print self.StateObject.target_vars["TARGET_HA"]
        
        if self.old_time != c_lmst:
            self.old_time = c_lmst
            if self.controlPanel:
                self.controlPanel.lockDomeMotion(0)
                self.controlPanel.lockDomeShutter(0)
                #self.stbMain.SetStatusText("System Idle", 1)

            dome_motion = self.pstack.ProcessTypeRunning('DOME_MOTION')
            shutdown_enabled = self.StateObject.shutdown_vars["SHUTDOWN_ENABLED"]
            shutdown_active = self.pstack.ProcessTypeRunning('SYSTEM_SHUTDOWN')
            rain_pulse = False #FIXME
            
            # check for shutdown conditions (add: dome_motion == False )
            if shutdown_enabled and shutdown_active != True:
                # check for shutdown conditions, this will be moved off somewhere else
                # do all the shutdown checks over here
                if self.StateObject.checkShutdownConditions():
                    msg = "System shutdown time has elapsed, starting shutdown procedure NOW!"
                    self.writeOutput(msg, timestamp=True)
                    #self.controlPanel.ra_qin.put(("STOP", None))
                    #self.controlPanel.dec_qin.put(("STOP", None))
                    #self.controlPanel.ra_qin.put(("CLOSE", None))
                    #self.controlPanel._RACtrl.join()
                    #self.controlPanel.dec_qin.put(("CLOSE", None))
                    #self.controlPanel._DECCtrl.join()
                    self.startSystemShutdown()
                
                elif rain_pulse:
                    msg = "PERCIPITATION DETECTED BY SENSOR, starting shutdown procedure NOW!"
                    self.writeOutput(msg, timestamp=True)
                    self.startSystemShutdown()
                    
            if self.StateObject.isTarget() and not self.testModeEnabled:
                self.StateObject.updateSystemTargetObject(True)
                
            # macro execution stuff
            if self.pstack.ProcessTypeRunning('MACRO_RUNNING'):
                print("process macro step")
                self.processMacroStep()
            
        return 1
    
    def OnClose(self, evt):
        # check if there are any processes running, if so warn the user
        if len(self.pstack._process_queue) > 0:
            dlg = wx.MessageDialog(self, 
                "Control processes are still active. Exiting may cause " + \
                "erratic behaviour of attached devices.\nWould you like " + \
                "to exit wxDomeTracker?", 
                'Processes Running', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING)
                
            if dlg.ShowModal() == wx. ID_YES:
                self.StateObject.SaveHWInterfaces(os.path.join(CONFIG_DIRECTORY, "settings.conf"))
                if self.argoNavisConnected:
                    self.argoConnectThread.abort = True
                
                #self.controlPanel.ra_qin.put(("STOP", None))
                #self.controlPanel.dec_qin.put(("STOP", None))
                self.controlPanel.ra_qin.put(("CLOSE", None))
                self.controlPanel._RACtrl.join()
                self.controlPanel.dec_qin.put(("CLOSE", None))
                self.controlPanel._DECCtrl.join()

                    
                self.Destroy()
                sys.exit()
        else:
            dlg = wx.MessageDialog(None, 'Would you like to exit wxDomeTracker?', 
                'Confirm Exit', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
            
            if dlg.ShowModal() == wx. ID_YES:
                self.StateObject.SaveHWInterfaces(os.path.join(CONFIG_DIRECTORY, "settings.conf"))
                if self.argoNavisConnected:
                    self.argoConnectThread.abort = True
                    
                self.Destroy()
                sys.exit()

    def OnShutdown(self, evt):
        if not self.pstack.ProcessTypeRunning('SYSTEM_SHUTDOWN'):
            dlg = wx.MessageDialog(None, 'Would you like to initiate a complete system shutdown?', 'Question', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
            
            if dlg.ShowModal() == wx. ID_YES:
                msg = "User has initiated a complete SHUTDOWN of the observing facility."
                self.writeOutput(msg, timestamp=True)
                #self.tmrDomeTrackerPoll.Stop()
                self.startSystemShutdown()
        else:
            dlg = wx.MessageDialog(None, 'There is a system shutdown currently in progress.', 'Error', 
                wx.OK | wx.ICON_ERROR)
            
            dlg.ShowModal()
    
    def shutdownCompleted(self, state):
        #self.StateObject.shutdown_vars["SHUTDOWN_ACTIVE"] = False
        self.pstack.PopProcessType('SYSTEM_SHUTDOWN')
        self.StateObject.shutdown_vars["SHUTDOWN_ENABLED"] = False
        
        self.StateObject.macros_vars = {"MACRO_NAME" : None,
           #"MACRO_RECORDING"        : False,
           "MACRO_EVENT_RECORD"     : False,
           #"MACRO_RUNNING"          : False,
           "MACRO_FLAGS"            : (False, False, False, False),
           "MACRO_SCALE"       : 0,
           "MACRO_FILE_PATH"        : None
          }
        
        self.pstack.PopProcessType('MACRO_RECORDING')
        self.pstack.PopProcessType('MACRO_RUNNING')
        #print self.StateObject.macros_vars
        #self.stbMain.SetStatusText('Dome Tracking OFF', 3) # set the status indicator
        self.stbMain.SetStatusText("Manual Shutdown", 2)
                
        #print self.pstack._process_queue
        
    def setDomeAction(self, state):
        # what does this function do?
        
        #self.StateObject.dome_vars["DOME_MOTION"] = state.data
        #self.StatusReset()
        self.pstack.PopProcessType('DOME_MOTION')
        #print self.pstack._process_queue

    #def setWeatherStatus(self, current_conditions):
    #    self.stbMain.SetStatusText(current_conditions.data, 1)
        #conditions = "Current weather conditions: {0}".format(current_conditions.data)
        #self.writeOutput(conditions)

    def OnServerSettings(self, evt):
        dlg = ConfigureServerFrame(self)
        print self.StateObject.server_vars["SERVER_ON_START"]
        dlg.SetValues(self.StateObject.server_vars["SERVER_ON_START"], 
            self.StateObject.server_vars["SERVER_HOST_ADDR"], 
            self.StateObject.server_vars["SERVER_PORT"],
            self.StateObject.server_vars["SERVER_REFRESH"])
        
        if dlg.ShowModal() == wx.ID_OK:
            (serverOnStart, hostaddress, port, refreshrate) = dlg.GetValues()
            
            self.StateObject.server_vars["SERVER_ON_START"] = serverOnStart
            self.StateObject.server_vars["SERVER_HOST_ADDR"] = hostaddress
            self.StateObject.server_vars["SERVER_PORT"] = port
            self.StateObject.server_vars["SERVER_REFRESH"] = refreshrate
            
        dlg.Destroy()
    
    def serverStatus(self, status):
        if status.data == 1:
            self.writeOutput('Stellarium client connection established.')
        else:
            pass # derp

    def SaveConfigFiles(self):
        server_conf_file = os.path.join(CONFIG_DIRECTORY, "settings.conf")
        self.StateObject.SaveHWInterfaces(server_conf_file)

    def LoadConfigFiles(self):
        server_conf_file = os.path.join(CONFIG_DIRECTORY, "settings.conf")
        f = open(server_conf_file, 'r')
        lines = f.readlines()
           
        for line in lines:
            line = line.rstrip("\n")
            var, val = line.split("=") 
            if line != '':
                if var == "hostname":
                    self.hostaddress = str(val)
                elif var == "port":
                    self.port = int(float(val))
                elif var == "sendrate":
                    self.refreshrate = int(float(val))
                elif var == "start":
                    self.serverOnStart = int(float(val))
                elif var == "serial":
                    pass
                elif var == "baudrate":
                    pass
                elif var == "pollinterval":
                    pass
                elif var == "parallel":
                    pass
                else:
                    print("Invalid token in 'server.conf' file")
        
        f.close()

    def loadSessionProfile(self, evt=None):
        pass
    
    def saveSessionProfile(self, evt=None):
        pass
        
    def writeFromThread(self, msg):
        ts = time.strftime("%Y-%m-%d %H:%M:%S")
        text = "[%s]: %s" % (ts, msg.data)
        #self.txtLogOutput.AppendText(text)
        self.INFODISPLAY.writeLine(text)
        f = open(self.logFilePath, 'a')
        f.write(text)
        f.close()

    def writeOutput(self, msg, timestamp=True):
        if timestamp:
            ts = time.strftime("%Y-%m-%d %H:%M:%S")
            text = "[%s]: %s" % (ts, msg)
            #self.txtLogOutput.AppendText(text)
            self.INFODISPLAY.writeLine(text)
            f = open(DEFAULT_LOG_FILE, 'a')
            f.write('\n{0}'.format(text))
            f.close()
        else:
            self.INFODISPLAY.writeLine(msg)
            #self.txtLogOutput.AppendText(str(msg)+"\n")

    def StatusReset(self):
        #print self.pstack._process_queue
        #self.setSystemStatus(False)
        #self.stbMain.SetStatusText("System Idle", 1)
        pass

    def OnSIMBAD(self, evt, name=None):
        #self.MacroRecorderObject.saveMacro()
        if self.StateObject.isTarget():
            if not name:
                name = self.StateObject.target_vars["TARGET_NAME"]
                
            obj_name = name.replace(' ' , '+')        
            url = "http://simbad.u-strasbg.fr/simbad/sim-basic?Ident=%s&submit=SIMBAD+search" % obj_name
            webbrowser.open(url)
        else:
            dlg = wx.MessageDialog(self, "Cannot lookup target information, no target object specified.", 'Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

# ==============================================================================
# SYSTEM SHUTDOWN
# ==============================================================================

    def startSystemShutdown(self):
        #self.StateObject.shutdown_vars["SHUTDOWN_ACTIVE"] = True
        self.stbMain.SetStatusText("SHUTTING DOWN", 2)

        if self.StateObject.dome_vars["TRACK_ENABLED"]:
            self.tmrDomeTrackerPoll.Stop()
            #self.stbMain.SetStatusText('Dome Tracking OFF', 3) # set the status indicator
        
        # unload the active macro
        self.macro_buffer = [] # reset buffer
        self.StateObject.macros_vars = {"MACRO_NAME" : None,
           #"MACRO_RECORDING"        : False,
           "MACRO_EVENT_RECORD"     : False,
           #"MACRO_RUNNING"          : False,
           "MACRO_FLAGS"            : (False, False, False, False), # dome motion, telescope motion, power, focus
           "MACRO_SCALE"       : 0, # 0 = siderial, 1 = local time
           "MACRO_FILE_PATH"        : None
          }
        
        self.pstack.PopProcessType('MACRO_RECORDING')
        self.pstack.PopProcessType('MACRO_RUNNING')
        stb_text = "No macro loaded"
        self.stbMain.SetStatusText(stb_text, 3)
        
        # continue the shutdown process
        shutdownThread = execf.ThreadedSystemShutdown(self.controlPanel.dc_qin, self.controlPanel.StopTracking)

        shutdownThread.setDaemon(True)
        shutdownThread.start()
        
        self.pstack.PushProcess(0, 'SYSTEM_SHUTDOWN', '', 1, shutdownThread) # fix this pointer
        
        msg = "*** SHUTDOWN PROCEEDURE INITIATED ***"
        self.writeOutput(msg, timestamp=True)
        
        #self.StateObject.dome_vars["TRACK_ENABLED"] = False
        self.setSystemStatus("Shutting Down")
        #self.stbMain.SetStatusText("Shutting Down", 1)
        
        #print self.pstack._process_queue
        
        return 1
                    
# ==============================================================================
# PLANETARIUM SERVER
# ==============================================================================

#   Basic server for planetarium software. Since this software does not provide
#   a graphical display for pointing, it allows external software to connect and
#   display the position of software. Currently on  is supported.

    def serverDataUpdate(self, evt=None):
        if self.StateObject.server_vars["SERVER_ACTIVE"]:
            hh, mm, ss = self.StateObject.telescope_vars["TELESCOPE_RA"]
            targetRA = obsmathlib.RightAscensionToHours(hh, mm, ss)
            deg, mm, ss = self.StateObject.telescope_vars["TELESCOPE_DEC"]
            targetDEC = obsmathlib.DeclinationToDegrees(deg, mm, ss)
            
            srv_ra, srv_dec = obsmathlib.epochJ2k(targetRA, targetDEC)
            
            TIME = 0 # unused
            RA = int(srv_ra * (0x100000000 / 24.0))
            DEC = int(srv_dec * (0x40000000 / 90.0))
            
            self.extsrv.setData(TIME, RA, DEC)

# ==============================================================================
# DISPLAY UPDATE CODE
# ==============================================================================

#   Update "real-time" information. Done by either reading it from variables or
#   calculating it. Shutdown conditions are also checked here

    def updateInformationDisplay(self, event):
        self.StateObject.updateSystemTime()
        currentUTC, currentLT, currentLMST, currentJD = self.StateObject.getSystemTimeParameters()

        year = currentUTC[0]
        month = currentUTC[1]
        day = currentUTC[2]
        hour = currentUTC[3]
        min = currentUTC[4]
        sec = currentUTC[5]
        N = currentUTC[7]
        
        strUTC = "%02d-%02d-%04d %02d:%02d:%02d" % (day, month, year, hour, min, sec)
        strLMST = "%02d:%02d:%02d" % (currentLMST[0], currentLMST[1], currentLMST[2])
        strJulianDate = "%.5f" % currentJD
        
        #self.pnlSideBar.lblUTC.SetLabel(strUTC)
        #self.pnlSideBar.lblLMST.SetLabel(strLMST)
        #self.pnlSideBar.lblJulianDate.SetLabel(strJulianDate)
        self.INFODISPLAY.SetUniversalTimeText(strUTC)
        self.INFODISPLAY.SetJulianDateText(strJulianDate)
        self.INFODISPLAY.SetSideralTimeText(strLMST)
        
        year = currentLT[0]
        month = currentLT[1]
        day = currentLT[2]
        hour = currentLT[3]
        min = currentLT[4]
        sec = currentLT[5]
        N = currentLT[7]

        strLT = "%02d-%02d-%04d %02d:%02d:%02d" % (day, month, year, hour, min, sec)
        #self.pnlSideBar.lblLocalTime.SetLabel(strLT)
        self.INFODISPLAY.SetLocalTimeText(strLT)
        
        # Telescope parameter calculations, formatting and display
        self.UpdateTelescopePosition() # update telescope position
        telescopeRA = self.StateObject.telescope_vars["TELESCOPE_RA"]
        telescopeDec = self.StateObject.telescope_vars["TELESCOPE_DEC"]
        telescope_data = self.StateObject.computeTelescopeParameters(telescopeRA, telescopeDec)
        telescopeRA, telescopeDec, telescopeHA, telescopeAzmth, telescopeAlt, telescopeAirmass = telescope_data
        #self.SKYDISPLAY.SetTelescopePosition(telescopeAzmth, telescopeAlt)
        
        ra_guide = ""
        dec_guide = ""
        
        # guidence enabled
        if self.StateObject.isTarget():
            target_data = self.StateObject.getSystemTargetInfo()
            name = self.StateObject.target_vars["TARGET_NAME"]
            targetRA, targetDec, epoch, targetHA, targetAzmth, targetAlt, targetAirmass, targetColour = target_data
            extended_data = self.StateObject.target_vars["TARGET_DATA_EXT"]
            
            flt_targ_ra = obsmathlib.RightAscensionToHours(targetRA[0], targetRA[1], targetRA[2])
            flt_targ_dec = obsmathlib.DeclinationToDegrees(targetDec[0], targetDec[1], targetDec[2])
            
            flt_tele_ra = obsmathlib.RightAscensionToHours(telescopeRA[0], telescopeRA[1], telescopeRA[2])
            flt_tele_dec = obsmathlib.DeclinationToDegrees(telescopeDec[0], telescopeDec[1], telescopeDec[2])
            
            if flt_targ_ra >= flt_tele_ra:
                ra_guide = '(W)'
            elif flt_targ_ra < flt_tele_ra:
                ra_guide = '(E)'

            if flt_targ_dec >= flt_tele_dec:
                dec_guide = '(N)'
            elif flt_targ_dec < flt_tele_dec:
                dec_guide = '(S)'
        
        telescopeRA = "%02dh %02dm %02ds %s" % (telescopeRA[0], telescopeRA[1], telescopeRA[2], ra_guide)
        telescopeDec = "%02dd %02dm %02ds %s" % (telescopeDec[0], abs(telescopeDec[1]), abs(telescopeDec[2]), dec_guide)
        
        tscope_ha_hour, tscope_ha_min, tscope_ha_sec = telescopeHA
        if tscope_ha_sec < 0.0:
            sign = '-'
        else:
            sign = '+'
        
        telescopeHA = "%s%02dh %02dm %02ds" % (sign, abs(tscope_ha_hour), abs(telescopeHA[1]), abs(telescopeHA[2]))
        telescopeAzmth = "%.4f" % telescopeAzmth
        telescopeAlt = "%.4f" % telescopeAlt
        
        if self.digitalSettingCircles:
            self.digitalSettingCircles.coord_disp.SetRAText(telescopeRA)
            self.digitalSettingCircles.coord_disp.SetDecText(telescopeDec)
            self.digitalSettingCircles.coord_disp.SetHAText(telescopeHA)
            self.digitalSettingCircles.coord_disp.SetLMSTText(strLMST)
            self.digitalSettingCircles.coord_disp.Refresh()
        
        # update server
        self.serverDataUpdate()
        l_tele_text = [telescopeRA, telescopeDec, 
            telescopeHA, telescopeAzmth, telescopeAlt, 
            str('%.4f' % telescopeAirmass)]
        
        self.INFODISPLAY.SetTelescopeInfoText(l_tele_text)
        
        #self.pnlCoordDisplay.SetRAText(telescopeRA)
        #self.pnlCoordDisplay.SetDecText(telescopeDec)
        #self.pnlCoordDisplay.SetHAText(telescopeHA)
        
        #self.pnlCoordDisplay.Refresh()
        
        # shutdown condition checks
        dome_motion = self.pstack.ProcessTypeRunning('DOME_MOTION')
        tracking_mode = self.StateObject.dome_vars["TRACK_MODE"]
        tracking_enabled = self.StateObject.dome_vars["TRACK_ENABLED"]
        shutdown_active = self.pstack.ProcessTypeRunning('SYSTEM_SHUTDOWN')
                
        if self.StateObject.isTarget():            
            if extended_data:
                object_id = extended_data["HYG_ID"]
            else:
                object_id = -1
            
            #objDisplayData = {-1 : (name, targetAzmth, targetAlt, targetColour)} # ID = -1 since one object is being displayed
            #self.SKYDISPLAY.SetObjects(objDisplayData)
            #self.SKYDISPLAY.SetTarget(-1)
            
            l_trg_text = []
            
            targetRA = "%02dh %02dm %02ds" % (targetRA[0], targetRA[1], targetRA[2])
            targetDec = "%02dd %02dm %02ds" % (targetDec[0], abs(targetDec[1]), abs(targetDec[2]))

            target_ha_hour, target_ha_min, target_ha_sec = targetHA
            if target_ha_sec < 0.0:
                sign = '-'
            else:
                sign = '+'
                
            targetHA = "%s%02dh %02dm %02ds" % (sign, abs(target_ha_hour), abs(target_ha_min), abs(target_ha_sec))
            
            if targetAlt < 0.0:
                targetAlt = "%.4f (HRZN)" % targetAlt
            else:
                targetAlt = "%.4f" % targetAlt
                
            #self.pnlSideBar.lblTargetName.SetLabel(name)
            
            #self.pnlSideBar.lblTargetRightAscension.SetLabel(targetRA)
            #self.pnlSideBar.lblTargetDeclination.SetLabel(targetDec)
            #self.pnlSideBar.lblTargetEpoch.SetLabel("J" + str(epoch))
            #self.pnlSideBar.lblTargetHourAngle.SetLabel(targetHA)
            #self.pnlSideBar.lblTargetAzimuth.SetLabel("%.4f" % targetAzmth)
            #self.pnlSideBar.lblTargetAltitude.SetLabel(targetAlt)
            #self.pnlSideBar.lblTargetAirmass.SetLabel(str('%.4f' % abs(targetAirmass)))
            #self.pnlSideBar.lblTargetMotion.SetLabel(targetMotion)
            
            l_trg_text = [name, targetRA, targetDec, targetHA, 
                str(epoch), "%.4f" % targetAzmth, 
                targetAlt, str('%.4f' % abs(targetAirmass)), 
                extended_data]
            
            self.INFODISPLAY.SetTargetInfoText(l_trg_text)
                
        else:
            objDisplayData = None
            #self.SKYDISPLAY.SetObjects(objDisplayData)
            #self.SKYDISPLAY.SetTarget(None)
            #self.pnlSideBar.lblTargetName.SetLabel("No Target Set!")
            #self.pnlSideBar.lblTargetRightAscension.SetLabel("N/A")
            #self.pnlSideBar.lblTargetDeclination.SetLabel("N/A")
            #self.pnlSideBar.lblTargetHourAngle.SetLabel("N/A")
            #self.pnlSideBar.lblTargetAzimuth.SetLabel("N/A")
            #self.pnlSideBar.lblTargetAltitude.SetLabel("N/A")
            #self.pnlSideBar.lblTargetAirmass.SetLabel("N/A")
            #self.pnlSideBar.lblTargetMotion.SetLabel("N/A")
        
        shutdown_hour = self.StateObject.shutdown_vars["SHUTDOWN_HOUR"]
        shutdown_min = self.StateObject.shutdown_vars["SHUTDOWN_MINUTE"]
        shutdown_enabled = self.StateObject.shutdown_vars["SHUTDOWN_ENABLED"]
        tracking_enabled = self.StateObject.dome_vars["TRACK_ENABLED"]
        
        # strShutdownTime = '%02d:%02d' % (shutdown_hour, shutdown_min)
        strShutdownEnabled = str(shutdown_enabled)
        
        if self.StateObject.dome_vars["TRACK_ENABLED"] == True:
            #self.stbMain.SetStatusText("Dome object tracking enabled.", 4)
            #self.lblDomeAzimuth.SetLabel(str('%.4f' % self.domeAzimuth))
            pass
        else:
            #self.stbMain.SetStatusText("Dome object tracking disabled.", 4)
            pass
            #self.lblDomeAzimuth.SetLabel("N/A")
        
        if shutdown_enabled:
            strShutdownTime = "%02d:%02d" % (shutdown_hour, shutdown_min)
        else:
            strShutdownTime = "Disabled"
        
        if self.StateObject.dome_vars["TRACK_ENABLED"]:
            strDomeTracking = "Enabled"
        else:
            strDomeTracking = "Disabled"
            
        #print self.hwc.getConnectionStatus()
        
        if self.hwc.getRainPulse():
            self.INFODISPLAY.SetRainSensorText('!!! RAINING !!!')
        else:
            self.INFODISPLAY.SetRainSensorText('READY/WAITING')

        #if not self.hwc.DetectVoltage():
        #    self.INFODISPLAY.SetHWCText('CONNECTED')
        #else:
        #    self.INFODISPLAY.SetHWCText('ISOLATED')
        
        #self.lblShutdownTime.SetLabel(strShutdownTime)
        #self.lblDomeTracking.SetLabel(strDomeTracking)
        
        self.INFODISPLAY.Redraw()
    
    def sendFromPlanetarium(self, o_name, o_ra, o_dec, o_type='STAR', o_mag=1.0, o_desc=None):
        if self.argoNavisConnected:
            # send object to on-board catalog
            if (o_desc == None):
                o_desc = 'NO DESCRIPTION'
            
            s_ra = '{0:0=2}:{1:0=2}:{2:0=2}'.format(o_ra[0], o_ra[1], o_ra[2])
            s_dec = '{0:0=+2}:{1:0=2}:{2:0=2}'.format(o_dec[0], 
                abs(o_dec[1]), abs(o_dec[2]))

            fp_cmd = 'fp `{0}|{1}|{2}|{3}|{4}|{5}`'.format(o_name, s_ra, 
                s_dec, o_type, o_mag, o_desc)
            
            wx.CallAfter(Publisher().sendMessage, "argo_tx", fp_cmd)
        
        return 1

    def updateArgoCoords(self, argo_thread):
        self.StateObject.interface_vars["ARGO_COORDINATES"] = argo_thread.data

    #   This function deals with the setting the telescope position from either
    #   being virtual or measured at the Argo Navis. This removes the need for
    #   doing checks elsewhere in the code.
        
    def UpdateTelescopePosition(self):
        telescope_mode = self.StateObject.telescope_vars["TELESCOPE_MODE"]
        if telescope_mode == 0:
            argo_coords = self.StateObject.interface_vars["ARGO_COORDINATES"]
            self.StateObject.telescope_vars["TELESCOPE_RA"] = argo_coords[0]
            self.StateObject.telescope_vars["TELESCOPE_DEC"] = argo_coords[1]
            
            self.INFODISPLAY.tel_mode = "Argo Navis"
            #self.pnlSideBar.lblTelescopeMode.SetLabel("Argo Navis")
        else:
            vt_coords = self.StateObject.telescope_vars["VIRTUAL_COORDINATES"]
            self.StateObject.telescope_vars["TELESCOPE_RA"] = vt_coords[0]
            self.StateObject.telescope_vars["TELESCOPE_DEC"] = vt_coords[1]
            
            self.INFODISPLAY.tel_mode = "Virtual Telescope"
            #self.pnlSideBar.lblTelescopeMode.SetLabel("Virtual Telescope")
        
    def testArgoConnection(self):
        success = False
        try:
            temp_argoterm = argoterm.ArgoInterface(0)
            if temp_argoterm.getRAD(2): # test if the argo responds, if it does, flag as connected
                success = True
                temp_argoterm.closeConnection()
            else:
                dlg = wx.MessageDialog(self, 
                    "Cannot resolve connection with Argo Navis, please check if the Argo Navis is powered on.", 
                    'Connection Error', wx.OK | wx.ICON_ERROR)
                dlg.ShowModal()
        except:
            dlg = wx.MessageDialog(self, 
                "Cannot open connection interface port for Argo Navis. Check connection and try again.", 
                'Connection Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
        
        return success
    
    #
    # Give this function coords of an object and it will output it's offset
    # relative to there the argo navis is at the time the function is called
    #
    def calculateArgoNavisOffset(self, ra_obj, dec_obj):
        ra_argo = self.StateObject.telescope_vars["ARGO_COORDINATES"][0]
        dec_argo = self.StateObject.telescope_vars["ARGO_COORDINATES"][1]
        
        ra_offset = 0
        dec_offset = 0
        
        return ra_offset, dec_offset
    
    # this needs float hours and degrees
    def applyArgoNavisOffset(self, ra, dec):
        if self.StateObject.self.calibr_vars['CALIBRATION_ENABLED']:
            ra_offset = self.StateObject.self.calibr_vars["CALIBRATION_RA_OFFSET"]
            dec_offset = self.StateObject.self.calibr_vars["CALIBRATION_DEC_OFFSET"]
            
            ra_new = ra + ra_offset
            dec_new = dec + dec_offset
            
            # correction rules for RA
            if (ra_new >= 24.0):
                ra_new = ra_new - 24.0
            elif (ra_new < 0.0):
                ra_new = 24.0 - ra_new
                
            # correction rules for declination
            if (dec_new >= 90.0):
                dec_new = dec_new - 90.0
            elif (dec_new <= -90.0):
                dec_new = -90.0 + dec_new
        
        return ra, dec

    def OnConnectInput(self, event):
        if self.mnuConnectInput.IsChecked():
            if self.testArgoConnection(): # test if the argo responds, if it does, flag as connected
                # since the connection succeeded, we create a comm thread
                self.argoConnectThread = ArgoCommunicationThread(0) # add to process manager someday
                self.argoConnectThread.setDaemon(True)
                self.argoConnectThread.start()
                self.argoNavisConnected = True
                self.INFODISPLAY.SetArgoConnectionStatus('CONNECTED')
                self.stbMain.SetStatusText('CONNECTED', 1)
                
                # ask if the user wants to set the agro for telescope telemetry
                dlg = wx.MessageDialog(None, 'Argo Navis connected, set as pointing device?', 'Question', 
                    wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
                
                if dlg.ShowModal() == wx. ID_YES:
                    self.StateObject.telescope_vars["TELESCOPE_MODE"] = 0
            else:
                self.mnuConnectInput.Check(False)
        else:
            self.argoNavisConnected = False
            self.argoConnectThread.abort = True
            self.StateObject.telescope_vars["TELESCOPE_MODE"] = 1
            self.INFODISPLAY.SetArgoConnectionStatus('DISCONNECTED')
            self.stbMain.SetStatusText('DISCONNECTED', 1)
            
    def cancelAction(self, evt):
        # probably not thread safe but it should work most of the time.
        if self.pstack.ProcessTypeRunning('SYSTEM_SHUTDOWN'):
            #self.shutdownThread.abort = True
            # this make the assumption there is only one shutdown thread active
            pid = self.pstack.FindProcessID('SYSTEM_SHUTDOWN')
            if len(pid) > 0:
                proc_dat = self.pstack.GetProcess(pid[0])
                t_pointer = proc_dat[4]
                t_pointer.abort = True
                
                # stop everything manually
                self.hwc.resetDataRange()
                self.hwc.resetControlRange()

                msg = "Sent abort signal to shutdown routine. Please wait while thread exits."
                self.writeOutput(msg, True)
            else:
                # basic action stop
                self.hwc.resetDataRange()
                self.hwc.resetControlRange()

#   Graphical dialog for setting the telescope position and mode.

    def OnTelescopePosition(self, evt):
        dlg = TelescopeModeFrame(self)
        dlg.SetValues(self.StateObject.telescope_vars["TELESCOPE_MODE"], self.StateObject.telescope_vars["VIRTUAL_COORDINATES"], self.argoNavisConnected)
        
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
            self.StateObject.telescope_vars["TELESCOPE_MODE"] = vals[0]
            self.StateObject.telescope_vars["VIRTUAL_COORDINATES"] = vals[1]
            
        dlg.Destroy()
            
# ==============================================================================
# DOME TRACKING CODE 
# ==============================================================================

#   Dome object tracking code. Attempts to keep the dome opening at the
#   telescope's line-of-sight. System is far from perfect given the limitations
#   of the current hardware. As a result the user cannot alter the polling
#   interval and threashold since they were found using trial and error.

    def initialiseDomeTracker(self):
        if self.StateObject.dome_vars["TRACK_MODE"] == 0:
            targetRA = self.StateObject.target_vars["TARGET_RA"]
            targetDEC = self.StateObject.target_vars["TARGET_DEC"]
        else:
            targetRA = self.StateObject.telescope_vars["TELESCOPE_RA"]
            targetDEC = self.StateObject.telescope_vars["TELESCOPE_DEC"]
        
        cLMST = obsmathlib.calculateLMST()
        self.domeObjectAlt = obsmathlib.calculateAltitude(targetRA, targetDEC, cLMST);
        self.domeObjectAzmth = obsmathlib.calculateAzimuth(targetRA, targetDEC, self.domeObjectAlt, cLMST);
        self.domeObjectAirmass = obsmathlib.calculateAirmass(targetRA, targetDEC, cLMST)
        self.domeObjectAirmass = (int(self.domeObjectAirmass * 1000.)) / 1000.  
        
        currentHA = obsmathlib.calculateHourAngle(targetRA, cLMST)
        currentHA = (int(currentHA * 1000.)) / 1000.
        
        self.domeAzimuth = self.domeObjectAzmth
        self.oldDomeObjectAzmth = self.domeObjectAzmth
        
        if self.StateObject.dome_vars["TRACK_MODE"] == 0:
            name = self.StateObject.target_vars["TARGET_NAME"]
        else:
            name = "telescope"
            
        msg = "Dome automation procedure started. Following position of '%s'." % name
        self.writeOutput(msg, timestamp=True)
        msg = "Realignment check will occur every two minutes, movement threshold set at %s degrees." % 3
        self.writeOutput(msg, timestamp=True)
        
        delay = 60000*2
        self.tmrDomeTrackerPoll.Start(delay)
        
        #self.stbMain.SetStatusText('Dome Tracking ON', 3) # set the status indicator
    
    def domeTrackingPollEvent(self, evt):
        self.domeTrackingProcedure()

#   Dome tracking main loop. Executed using a timer event given it is mostly
#   non-blocking and thus requires no threading.

    def domeTrackingProcedure(self):
        AZIMUTH_THRESHOLD = 3
        if self.StateObject.dome_vars["TRACK_MODE"] == 0:
            targetRA = self.StateObject.target_vars["TARGET_REF_RA"]
            targetDEC = self.StateObject.target_vars["TARGET_REF_DEC"]
        else:
            targetRA = self.StateObject.telescope_vars["TELESCOPE_RA"]
            targetDEC = self.StateObject.telescope_vars["TELESCOPE_DEC"]
                                    
        # "Polling period elapsed, checking alignment of dome."        
        self.oldDomeObjectAzmth = self.domeObjectAzmth
        
        cLMST = obsmathlib.calculateLMST()
        currentHA = obsmathlib.calculateHourAngle(targetRA, cLMST)
        self.domeObjectAlt = obsmathlib.calculateAltitude(targetRA, targetDEC, cLMST);
        self.domeObjectAzmth = obsmathlib.calculateAzimuth(targetRA, targetDEC, self.domeObjectAlt, cLMST);
        currentHA = (int(currentHA*1000.)) / 1000.
        self.domeObjectAirmass = (int(self.domeObjectAirmass*1000.)) / 1000. 
        
        azimuthDifference = self.domeObjectAzmth - self.domeAzimuth
        
        if azimuthDifference > 300: 
            azimuthDifference -= 360
        
        if currentHA > 3:
            #message = "Hour angle > 3, setting azimuth threshold to 4 degrees."
            AZIMUTH_THRESHOLD = 4
            msg = "Hour angle > 3, setting azimuth threshold to 4 degrees."
                
            self.writeOutput(msg, timestamp=True)
        
        if azimuthDifference >= AZIMUTH_THRESHOLD and self.pstack.ProcessTypeRunning('SYSTEM_SHUTDOWN') != True:
            if math.fabs(azimuthDifference) >= 1.2:
                hwc = execf.ThreadedDomeRotation('lpt1', 'cw', 1.5)
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CW", 1)
                self.setSystemStatus("Dome Rotate CW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CW 1.5 degrees to compensate." % \
                    '%.4f' % math.fabs(azimuthDifference)
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth;
            elif math.fabs(azimuthDifference) >= 0.6 and math.fabs(azimuthDifference) < 1.2:
                hwc = execf.ThreadedDomeRotation('lpt1', 'cw', math.fabs(azimuthDifference * 0.45))
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CW", 1)
                self.setSystemStatus("Dome Rotate CW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CW %s degrees to compensate." % \
                    ('%.4f' % math.fabs(azimuthDifference), math.fabs(azimuthDifference * 0.45))
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth
            else:
                hwc = execf.ThreadedDomeRotation('lpt1', 'cw', math.fabs(azimuthDifference * 2.0))
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CW", 1)
                self.setSystemStatus("Dome Rotate CW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CW %s degrees to compensate." % \
                    ( '%.4f' % math.fabs(azimuthDifference), math.fabs(azimuthDifference * 2.0))
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth
            
            #print self.pstack._process_queue
        
        elif azimuthDifference <= (-1.0 * AZIMUTH_THRESHOLD) and self.totalShutdownActive != True:
            if math.fabs(azimuthDifference) >= 1.2:
                hwc = execf.ThreadedDomeRotation('lpt1', 'ccw', 1.5)
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CCW", 1)
                self.setSystemStatus("Dome Rotate CCW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CCW 1.5 degrees to compensate." % \
                    '%.4f' % math.fabs(azimuthDifference)
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth
                
            elif math.fabs(azimuthDifference) >= 0.6 and math.fabs(azimuthDifference) < 1.2:
                hwc = execf.ThreadedDomeRotation('lpt1', 'ccw', math.fabs(azimuthDifference * 0.45))
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CCW", 1)
                self.setSystemStatus("Dome Rotate CCW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CCW %s degrees to compensate." % \
                    ('%.4f' % math.fabs(azimuthDifference),  math.fabs(azimuthDifference * 0.45))
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth
            else:
                hwc = execf.ThreadedDomeRotation('lpt1', 'ccw', math.fabs(azimuthDifference * 2.0))
                hwc.setDaemon(True)
                hwc.start()
                #self.StateObject.dome_vars["DOME_MOTION"] = True
                self.pstack.PushProcess(0, 'DOME_MOTION', 'Dome motion thread', 1, hwc)
                #self.stbMain.SetStatusText("Rotate CCW", 1)
                self.setSystemStatus("Dome Rotate CCW")
                msg = "Azimuth difference exceeded threshold (%s degrees), moving dome CCW %s degrees to compensate." % \
                    ('%.4f' % math.fabs(azimuthDifference), math.fabs(azimuthDifference * 2.0))
                    
                self.writeOutput(msg, timestamp=True)
                self.domeAzimuth = self.domeObjectAzmth
            
            #print self.pstack._process_queue
            
        else:
            msg = "Dome and object alignment within threshold (%s degrees), no action taken this polling period." % ('%.4f' % azimuthDifference)
            self.writeOutput(msg, timestamp=True)

    def OnLoadConfig(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        print "Event handler `OnLoadConfig' not implemented!"
        event.Skip()

    def OnSaveConfig(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        print "Event handler `OnSaveConfig' not implemented!"
        event.Skip()

    def OnSetupConnections(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        dlg = ConnectionsFrame(self)
        term_addr = self.StateObject.interface_vars["TERMINAL_COM_ADDR"]
        term_baud = self.StateObject.interface_vars["TERMINAL_BAUDRATE"]
        term_poll = self.StateObject.interface_vars["TERMINAL_INTERVAL"]
        vals = dlg.SetValues(term_addr, term_baud, term_poll)
        
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
            self.StateObject.interface_vars["TERMINAL_COM_ADDR"] = vals[0]
            self.StateObject.interface_vars["TERMINAL_BAUDRATE"] = vals[1]
            self.StateObject.interface_vars["TERMINAL_INTERVAL"] = vals[2]
        
        dlg.Destroy()

    def OnHardwareInterface(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        print "Event handler `OnHardwareInterface' not implemented!"
        event.Skip()

    def OnLogging(self, event):
        file_name = self.logFilePath
        dlg = wx.FileDialog(
            self, message="Specify log output file", defaultDir=os.getcwd(), 
            defaultFile=file_name, wildcard="Text Files (*.txt)|*.txt", style=wx.SAVE|wx.CHANGE_DIR
            )
        dlg.Center()
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if not (path.lower()).endswith('.txt'):
                path = path + '.txt'
            
            self.logFilePath = path
        
        dlg.Destroy()

    def OnLogFont(self, evt):
        c_font = self.INFODISPLAY.GetFontSize()
        dlg = wx.SingleChoiceDialog(
                self, 'Select new font size (currently {0}).'.format(c_font), 
                'Output Display Font Size', 
                ['8', '10', '11', '12', '14', '16', '18', '20', '24'], 
                wx.CHOICEDLG_STYLE
                )

        if dlg.ShowModal() == wx.ID_OK:
            font_size = int(float(dlg.GetStringSelection()))
            self.INFODISPLAY.SetFontSize(font_size)
            
        dlg.Destroy()

    def OnRainSensor(self, event):
        dlg = RainSensorFrame(self)
        
        #dlg.SetValues(self.serverOnStart, self.hostaddress, self.port, self.refreshrate)
        
        if dlg.ShowModal() == wx.ID_OK:
            pass
                
        dlg.Destroy()

    def OnCAM(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        print "Event handler `OnCAM' not implemented!"
        event.Skip()

    def OnShowSettingCircles(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        if self.digitalSettingCirclesCount < 1:
            self.digitalSettingCircles = DigitalSettingCircles(self)
            self.digitalSettingCircles.Show()

    def OnSetTarget(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        macro_mode = self.StateObject.macros_vars["MACRO_SCALE"]
        if (self.pstack.ProcessTypeRunning('MACRO_RUNNING') != True):
            dlg = SetTargetObject(self)
            
            if self.StateObject.isTarget():
                target_dat = self.StateObject.target_vars
                ra, dec, name = (target_dat["TARGET_REF_RA"], 
                    target_dat["TARGET_REF_DEC"], 
                    target_dat["TARGET_NAME"])
                    
                dlg.SetValues(ra, dec, name)
            
            if dlg.ShowModal() == wx.ID_OK:
                object_data = dlg.GetValues()
                if object_data[0] == 0:
                    ra, dec, name = object_data[1]
                    self.StateObject.setSystemTargetObject(ra, dec, name, 2000.0)
                    self.StateObject.target_vars["TARGET_DATA_EXT"] = None
                    self.StateObject.target_vars["TARGET_COLOUR"] = 0.0
                    
                elif object_data[0] == 1:
                    obj_database = object_data[1]
                    
                    ra = obj_database["RA"]
                    dec = obj_database["DEC"]
                    name = obj_database["DISPLAY_NAME"]
                    
                    self.StateObject.setSystemTargetObject(ra, dec, name, 2000.0)
                    self.StateObject.target_vars["TARGET_DATA_EXT"] = obj_database
                    self.StateObject.target_vars["TARGET_COLOUR"] = 0.0
                
                #self.writeOutput([self.StateObject.target_vars, self.StateObject.telescope_vars, self.StateObject.dome_vars], False)
                msg = "Current target object set to '%s'." % name
                self.writeOutput(msg, True)
                #self.displayTargetObjectInfo()
                
            dlg.Destroy()
            
        else:
            dlg = wx.MessageDialog(self, "Cannot change target object when macro is running.", 'Warning', wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            
    def displayTargetObjectInfo(self):
        if self.StateObject.target_vars:
            extended_data = self.StateObject.target_vars["TARGET_DATA_EXT"]
            if extended_data:
                msg = "\n*** DATABASE RECORD DETAILS FOR CURRENT TARGET ***\n"
                self.writeOutput(msg, False)
                msg = "\tTARGET NAME: %s\n" % (self.StateObject.target_vars["TARGET_NAME"])
                self.writeOutput(msg, False)
                msg = "OBJECT ALIASES"
                self.writeOutput(msg, False)
                msg = "\tDATABASE ID: %s" % (extended_data["HYG_ID"])
                self.writeOutput(msg, False)
                msg = "\tHIP: %s" % (extended_data["HIP"])
                self.writeOutput(msg, False)
                msg = "\tHD: %s" % (extended_data["HD"])
                self.writeOutput(msg, False)
                msg = "\tGLIESE: %s" % (extended_data["GLIESE"])
                self.writeOutput(msg, False)
                msg = "\tBAYER-FLAMSTEED: %s" % (extended_data["BF"])
                self.writeOutput(msg, False)
                msg = "\tPROPER NAME: %s" % (extended_data["PROPER_NAME"])
                self.writeOutput(msg, False)
                msg = "\nPHYSICAL DETAILS"
                self.writeOutput(msg, False)
                msg = "\tRIGHT ASCESNION: %s" % (str(extended_data["RA"]))
                self.writeOutput(msg, False)
                msg = "\tDECLINATION: %s" % (str(extended_data["DEC"]))
                self.writeOutput(msg, False)
                msg = "\tDISTANCE: %s parsecs" % (str(extended_data["DISTANCE"]))
                self.writeOutput(msg, False)
                msg = "\tMAGNITUDE: %s" % (str(extended_data["MAGNITUDE"]))
                self.writeOutput(msg, False)
                msg = "\tABSOLUTE MAGNITUDE: %s" % (str(extended_data["ABS_MAGNITUDE"]))
                self.writeOutput(msg, False)
                msg = "\tSPECTRUM: %s" % (extended_data["SPECTRUM"])
                self.writeOutput(msg, False)
                msg = "\tCOLOUR INDEX: %s" % (str(extended_data["COLOUR_INDEX"]))
                self.writeOutput(msg, False)

                            #"RA"            : obj[7], 
                            #"DEC"           : obj[8], 
                            #"PROPER_NAME"   : obj[6], 
                            #"DISTANCE"      : obj[9], # in pc
                            #"MAGNITUDE"     : obj[10], 
                            #"ABS_MAGNITUDE" : obj[11], 
                            #"SPECTRUM"      : obj[12], 
                            #"COLOUR_INDEX"  : obj[13], 
                            #"DISPLAY_NAME"  : name

    def OnCalibrateArgo(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        dlg = CalibrateArgoFrame(self)
        
        #dlg.SetValues(shutdownEnabled, 0, shutdownHour, shutdownMinute, self.hwc)
        
        if dlg.ShowModal() == wx.ID_OK:
            #(shutdownEnabled, shutdownHour, shutdownMinute) = dlg.GetValues()
            pass
        
        dlg.Destroy()

    def OnControlPanel(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        #if self.StateObject.gui_vars["GUI_CTRL_COUNT"] < 1:
        self.controlPanel.Show()

    def OnSessionCalculator(self, event=None):
        dlg = SessionsCalculatorFrame(self)
        dlg.Centre()
        
        if self.StateObject.target_vars:
            targetRA = self.StateObject.target_vars["TARGET_REF_RA"]
            cLMST = obsmathlib.calculateLMST()
            currentHA = obsmathlib.HoursToRightAscension(obsmathlib.calculateHourAngle(targetRA, cLMST))
            dlg.setHourAngle(currentHA)
            
        if dlg.ShowModal() == wx.ID_OK:
            pass
        
        dlg.Destroy()

    def OnRunAction(self, event=None):
        dlg = wx.SingleChoiceDialog(
                self, 'Select an action from the list to run.', 'Run Action',
                ['DOME_CCW', 'DOME_CW'], 
                wx.CHOICEDLG_STYLE
                )

        if dlg.ShowModal() == wx.ID_OK:
            action = dlg.GetStringSelection()
        else:
            return

        dlg.Destroy()        

        dlg = wx.TextEntryDialog(
                self, 'Enter action duration in seconds.',
                'Action Duration', '0.0')
        dlg.SetValue("5.0")

        if dlg.ShowModal() == wx.ID_OK:
            duration = dlg.GetValue() 
            
            try:
                duration = float(duration)
            except:
                dlg = wx.MessageDialog(self, 
                    "Invalid input for action duration.", 
                    'Input Error', wx.OK | wx.ICON_ERROR)
                    
                dlg.ShowModal()
                return
        else:
            return

        dlg.Destroy()

        self.lockMacro()
        self.stbMain.SetStatusText("MACRO ACTION", 0)                     
        self.macro_thread = execf.ThreadedHardwareMacroAction(self.controlPanel.dc_qin, action, duration)
        self.macro_thread.setDaemon(1)
        self.macro_thread.start()
        msg = "Running macro action {0} for duration of {1} seconds.".format(action, duration)
        self.writeOutput(msg)

    def OnSendParkData(self, evt=None):
        # send park position to Argo Navis, assumes the park is at -20 degrees for now
        if self.argoNavisConnected:
            c_lmst = self.StateObject.time_vars["CURRENT_LMST"]
            name = 'HOME'
            hh, mm, ss = self.StateObject.time_vars["CURRENT_LMST"]
            currentRA = obsmathlib.RightAscensionToHours(hh, mm, ss)
            deg, mm, ss = (-20, 0, 0)
            currentDEC = obsmathlib.DeclinationToDegrees(deg, mm, ss)
            # conver to j2000
            ra, dec = obsmathlib.epochJ2k(currentRA, currentDEC)
            # now back to tuple
            ra = obsmathlib.HoursToRightAscension(ra)
            dec = obsmathlib.DegreesToDeclination(dec)
            
            # send the data to the argo
            self.sendFromPlanetarium(name, ra, dec, 'STAR', 1.0, o_desc=None)
        else:
            dlg = wx.MessageDialog(self, 
                "Cannot send park data to Argo Navis FROM PLANETARIUM catalog, device not connected.", 
                'Argo Navis TX Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

    def OnSendTarget(self, evt=None):
        if self.argoNavisConnected:
            if self.StateObject.isTarget():
                name = self.StateObject.target_vars['TARGET_NAME']
                ra = self.StateObject.target_vars['TARGET_REF_RA']
                dec = self.StateObject.target_vars['TARGET_REF_DEC']
                self.sendFromPlanetarium(name, ra, dec, 'STAR', 1.0, o_desc=None)
            else:
                dlg = wx.MessageDialog(self, 
                    "No target object specified.", 
                    'Argo Navis TX Error', wx.OK | wx.ICON_ERROR)
                dlg.ShowModal()
        else:
            dlg = wx.MessageDialog(self, 
                "Cannot send target to Argo Navis FROM PLANETARIUM catalog, device not connected.", 
                'Argo Navis TX Error', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

    def OnTestMode(self, event):
        if self.mnuTestMode.IsChecked():
            self.testModeEnabled = True
            dlg = wx.MessageDialog(self, 
                "Test mode ENABLED, macros will record/execute relative to TELESCOPE HOUR ANGLE.", 
                'Test Mode', wx.OK | wx.ICON_WARNING)
            msg = "TEST MODE ENABLED, macros will record/execute relative to TELESCOPE HOUR ANGLE."
            self.writeOutput(msg)
                
            dlg.ShowModal()
        else:
            self.testModeEnabled = False
            msg = "TEST MODE DISENGAGED, macros actions will record/execute relative to computed TARGET HOUR ANGLE."
            self.writeOutput(msg)

    def OnPower(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        dlg = PowerFrame(self)
        shutdownHour = self.StateObject.shutdown_vars["SHUTDOWN_HOUR"]
        shutdownMinute = self.StateObject.shutdown_vars["SHUTDOWN_MINUTE"]
        shutdownEnabled = self.StateObject.shutdown_vars["SHUTDOWN_ENABLED"]
        
        dlg.SetValues(shutdownEnabled, 0, shutdownHour, shutdownMinute, self.hwc)
        
        if dlg.ShowModal() == wx.ID_OK:
            (shutdownEnabled, shutdownHour, shutdownMinute) = dlg.GetValues()

            self.StateObject.shutdown_vars["SHUTDOWN_HOUR"] = shutdownHour
            self.StateObject.shutdown_vars["SHUTDOWN_MINUTE"] = shutdownMinute
            self.StateObject.shutdown_vars["SHUTDOWN_ENABLED"] = shutdownEnabled

            if shutdownEnabled:
                self.stbMain.SetStatusText('Shutdown at %02d:%02d' % (shutdownHour, shutdownMinute), 2)
                msg = "Automatic shutdown enabled, procedure will occur at %02d:%02d local time." % (shutdownHour, shutdownMinute)
                self.writeOutput(msg, True)
            else:
                self.stbMain.SetStatusText('Manual Shutdown', 2)
                self.setShutdownStatus(False)
        
        dlg.Destroy()

    def OnStopTracking(self, evt):
        if self.StateObject.dome_vars["TRACK_ENABLED"]:
            dlg = wx.MessageDialog(None, 'Would you like to stop dome object tracking?', 'Question', 
                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
            
            if dlg.ShowModal() == wx. ID_YES:
                self.StateObject.dome_vars["TRACK_ENABLED"] = False
                self.tmrDomeTrackerPoll.Stop() # stop the timer, redunancy
                #self.stbMain.SetStatusText('Dome Tracking OFF', 3) # set the status indicator
                msg = "Dome automatic tracking session STOPPED by user."
                self.writeOutput(msg, True)

    def OnDomeTracking(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        dlg = DomeTrackingFrame(self)
        enabledOld = self.StateObject.dome_vars["TRACK_ENABLED"]
        
        if self.StateObject.isTarget():
            name = self.StateObject.target_vars["TARGET_NAME"]
            dlg.SetValues(self.StateObject.dome_vars["TRACK_ENABLED"], 
                self.StateObject.dome_vars["TRACK_MODE"], 
                name, 
                self.domeTrackingThresholdDegrees, 
                self.domeTrackingPollingInterval)
        else:
            dlg.SetValues(self.StateObject.dome_vars["TRACK_ENABLED"], 
                self.StateObject.dome_vars["TRACK_MODE"], 
                None, 
                self.domeTrackingThresholdDegrees, 
                self.domeTrackingPollingInterval)            
        
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
            self.StateObject.dome_vars["TRACK_ENABLED"] = vals[0]
            self.StateObject.dome_vars["TRACK_MODE"] = vals[1]
            self.domeTrackingPollingInterval = vals[3]
            self.domeTrackingThresholdDegrees = vals[2]
            
            if self.StateObject.dome_vars["TRACK_MODE"] == 1:
                mode = 'telescope'
            else:
                mode = self.StateObject.target_vars["TARGET_NAME"]
            
            if self.StateObject.dome_vars["TRACK_ENABLED"]:
                if self.StateObject.dome_vars["TRACK_ENABLED"] != enabledOld:
                    self.initialiseDomeTracker()
            else:
                if self.StateObject.dome_vars["TRACK_ENABLED"] != enabledOld:
                    if self.tmrDomeTrackerPoll.IsRunning():
                        self.tmrDomeTrackerPoll.Stop()
                
                        msg = "Dome automation procedure ENDED by user."
                        self.writeOutput(msg, timestamp=True)
        
        dlg.Destroy()

    def OnAbout(self, event): # wxGlade: wxDomeTrackerFrame.<event_handler>
        info = wx.AboutDialogInfo()
        info.Name = "wxDomeTracker"
        info.Version = APP_VERSION
        info.Copyright = "(C) 2008 - 2014 York University Observatory"
        info.Description = wordwrap(
            "Supervisory control and data acquisition (SCADA) developled for use at the York University Observatory.\n\nBased on the original DomeTracker by Jesse Rogerson.",
            350, wx.ClientDC(self))
        info.WebSite = ("http://www.physics.yorku.ca/observatory/", "York University Observatory Home Page")
        info.Developers = [ "Jesse Rogerson",
                            "Matthew Cutone",
                            "Ted Rudyk",
                            "Lianne Manzer",
                            "Leesann Sutherland"]
        
        licenseFile = open(os.path.join(DOC_DIRECTORY, "license.txt"), 'r')
        info.License = licenseFile.read()

        # Then we call wx.AboutBox giving it that info object
        wx.AboutBox(info)

# end of class wxDomeTrackerFrame

class DomeTrackerAppClass(wx.App):
    def OnInit(self):
        wx.InitAllImageHandlers()
        frmMain = wxDomeTrackerFrame(None, -1, "")
        self.SetTopWindow(frmMain)
        frmMain.Show()
        
        return 1

# end of class DomeTrackerAppClass

if __name__ == "__main__":
    print('This is a module, code must be executed though reference.')
