Source code for mascara_control

'''
.. module:: mascara_control
.. moduleauthor:: Julien Spronck, Anna-Lea Lesage
.. created:: Aug 2014
.. modified:: Jan 2015

Module to control MASCARA station (interface with hardware, sensors, ...)

Includes:
    - Class Station

Modification history:
    2015-05-30: now ignoring the SWASP rain sensor (errs.ALERT_RAIN) because of a
                malfunction. - JS
'''
__version__ = '15.03'

### ***  Latest Changes Record  *** ###
### 19.01.2015 (JS)     - Added support for PDUs (added Station.pdu_station, Station.pdu_computers, Station.turn_pdu_on(), Station.turn_pdu_off(), Station.reboot_pdu(), Station.get_pdu_status(), Station.turn_camera_on(), Station.turn_camera_off(), Station.turn_all_cameras_on(), Station.turn_all_cameras_off()
###                     - Modified Station.cleanexit(), Station.shutdown(), Station.reboot(), Station.restart_cameras(), Station.text_status(), Station.log_status()
###                     - Removed all calls to IP relay


import mascara_electronics as me
from DAQ import DAQ
import threading
import mascara_config as cfg
import mascara_credentials as creds
import time
from printlog import log, rollinglog, myprint
from numpy import nan
import traceback
import datetime
import re
import errorcodes as errs

def start_new_thread(target, args, queue=None, name=''):
    try:
        th = threading.Thread(target=target, args=args, name=name)
        th.start()
    except:
        errmsg = traceback.format_exc()
        myprint(errmsg, queue=queue, timestamp=False)

[docs]class Station(object): ''' Class representing the MASCARA station (all interface with the hardware is here) ''' def __init__(self, **kwargs): ''' Initialise the station control system. Optional keywords: name: name of the station queue: Queue object used to link the print statements to a graphical user interface. If None, it will simply print at the command line. ''' from numpy import zeros self.queue = kwargs.pop('queue', None) ## 1. The IP relay connection, which then turn all the rest on ##myprint('Connecting to the IP relay',queue= self.queue) ##self.iprelay = me.IPrelay(cfg.ipIPRelay, port=23) ## 1. PDUs myprint('Initializing PDUs', queue=self.queue) self.pdu_station = me.PDU(cfg.ipPDUStation) self.pdu_computers = me.PDU(cfg.ipPDUComputers) ## 2. Initialisation of all connections via the DAQ myprint('Initializing the DAQ', queue=self.queue) self.daq = DAQ() ## 3. Limit switches for the dome and the door myprint('Initializing the limit switches', queue=self.queue) self.dome_open = me.LimitSwitch(self.daq, cfg.chLSDomeOpenedDOut, cfg.chLSDomeOpened) self.dome_closed = me.LimitSwitch(self.daq, cfg.chLSDomeClosedDOut, cfg.chLSDomeClosed) self.door = me.LimitSwitch(self.daq, cfg.chLSDoorDOut, cfg.chLSDoor) ## 4. Power supplies for motors and Peltiers: myprint('Initializing the power supplies', queue=self.queue) self.ps1 = me.PowerSupply(self.daq, 1) self.ps2 = me.PowerSupply(self.daq, 2) ## 5. Motors: myprint('Initializing the motor relays', queue=self.queue) self.motor_onoff = me.Relay(self.daq, cfg.chMotorOnOff) self.motor_dir = me.Relay(self.daq, cfg.chMotorDir, inversed=True) ## 6. Peltiers myprint('Initializing the Peltiers relays', queue=self.queue) self.peltiers_onoff = me.Relay(self.daq, cfg.chPeltiersOnOff) self.peltiers_dir = me.Relay(self.daq, cfg.chPeltiersCoolHeat) ## 7. The 5 falt fields LEDS groups myprint('Initializing the LEDs', queue=self.queue) self.leds = {} self.leds['N'] = me.LEDS(self.daq, cfg.chLEDNorth) self.leds['S'] = me.LEDS(self.daq, cfg.chLEDSouth) self.leds['E'] = me.LEDS(self.daq, cfg.chLEDEast) self.leds['W'] = me.LEDS(self.daq, cfg.chLEDWest) self.leds['C'] = me.LEDS(self.daq, cfg.chLEDCenter) self.taking_flats = {led:False for led in self.leds} ## 8. The heaters for the windows myprint('Initializing the window heaters', queue=self.queue) self.heater = me.Heater(self.daq, cfg.chHeaters) ## 10. Connecting to the encoder: myprint('Initializing the encoder', queue=self.queue) self.encoder = me.Encoder() self.domePosition = self.get_encoder(init=True, verbose=False) self.encoder_measurements = [self.domePosition, self.domePosition, self.domePosition] self.deltacoder = 0 myprint('Listing all serial ports', queue=self.queue) Nports = me.listSerialPorts() if Nports <= 3: myprint("Only one RHSensor connected!!", queue=self.queue) ## is_shut_down will be set to True when shutting down the station ## (using self.shutdown()) self.is_shut_down = False self.maintenance = False ## domeIsMoving will be set to True during opening or closing self.domeIsMoving = False ## hasBeenPrepared is set to True after the beginning-of-the-night calibrations. ## If False when starting to expose => do calibrations self.hasBeenPrepared = False RHserial = [cfg.RHinEnclosure, cfg.RHinDome, cfg.RHoutside] ## 11. Initialise the humidity sensors self.rhsensors = {} myprint('Initializing the RH sensors', queue=self.queue) for nser in RHserial: try: tmp = me.RHSensor(serialNumber=nser) self.rhsensors[tmp.name] = tmp except Exception: myprint('No humidity sensor on this port', queue=self.queue, warning=True) ## Lock to prevent multiple simultaneous readings of the RH sensors ## from different threads self.rh_lock = threading.Lock() ## Lock to prevent multiple simultaneous readings of the pdus ## from different threads self.pdu_lock = threading.Lock() ## Lock to prevent multiple simultaneous readings of the weather file self.weather_lock = threading.Lock() ## . Define the status of the station self.status = 0 self.time_of_action = datetime.datetime.utcnow() subindex = ['Temp', 'Hum', 'Status'] ## dictionnary holding the last 5 sets of humidity and temperature ## measurements self.rh_measurements = {rhk:{k:zeros(1) for k in subindex} for rhk in self.rhsensors} for rhk in self.rhsensors: self.rh_measurements[rhk]['Status'] = ['OK'] self.weather = {} self.get_weather(verbose=False) ## turn on Power supply self.motor_onoff.turnOff() self.motor_dir.turnOff() myprint('Turning on the cameras ...', queue=self.queue) self.turn_all_cameras_on() # myprint('Turning on the heat exchanger ...', queue=self.queue) # self.turn_pdu_on(cfg.outletHeatExchanger) myprint('Turning on the station ...', queue=self.queue) self.turn_pdu_on(cfg.outletStation) self.ps1.turnOn(V=20)
[docs] def close_rh(self): ''' Short routine to close properly the serial connection with the RH sensors. To be updated with the other serial connections? ''' for d in self.rhsensors.itervalues(): d.close()
[docs] def check_power_supplies(self): myprint("PS1:", queue=self.queue) myprint("V = {0}".format(self.ps1.getVoltage()), queue=self.queue) myprint("C = {0}".format(self.ps1.getCurrent()), queue=self.queue) myprint("PS2:", queue=self.queue) myprint("V = {0}".format(self.ps2.getVoltage()), queue=self.queue) myprint("C = {0}".format(self.ps2.getCurrent()), queue=self.queue)
[docs] def is_dome_open(self, updateDomePosition=True): ''' is_dome_open checks if the dome is open using the limit switches and the position of the encoder. ''' if updateDomePosition: self.get_encoder(verbose=False) # find most likely encoder position # (value appearing most often in self.encoder_measurements) ct = {} for v in self.encoder_measurements: ct[v] = ct[v]+1 if v in ct else 1 domePos = max(ct, key=lambda v: ct[v]) ## note that this function returns true if the dome_open limit switch ## is pushed independently of the dome_closed limit switch. ## while this does not represent accurately the dome position, ## it is the safest because when this function is True, ## it will not try to open the dome. ## So, in the event that both limit switches are pushed and ## that the encoder position is unknown, this function is True ## (even though we do not actually know the dome position) return (self.dome_open.isPushed()) or (abs(domePos - cfg.OpenDomePosition) < 0.25)
[docs] def is_dome_closed(self, updateDomePosition=True): ''' is_dome_closed checks that the dome is properly closed ''' if updateDomePosition: self.get_encoder(verbose=False) # find most likely encoder position (value appearing most often in # self.encoder_measurements) ct = {} for v in self.encoder_measurements: ct[v] = ct[v]+1 if v in ct else 1 domePos = max(ct, key=lambda v: ct[v]) ## note that this function returns true if the dome_closed limit switch ## is pushed independently of the dome_open limit switch. ## while this does not represent accurately the dome position, ## it is the safest because when this function is True, ## it will not try to close the dome. ## So, in the event that both limit switches are pushed and ## that the encoder position is unknown, this function is True ##(even though we do not actually know the dome position) return (self.dome_closed.isPushed()) or (abs(domePos - cfg.ClosedDomePosition) < 0.25)
[docs] def is_door_open(self): ''' is the door open ''' return not self.door.isPushed()
[docs] def open_dome(self, timeout=25, writelog=True): ''' Opens the dome Keywords: timeout: integer (default 25 sec) writelog: boolean (default True) ''' myprint('Opening the dome', queue=self.queue, timestamp=False) if self.is_dome_open(): print 'Dome is already open' return if self.domeIsMoving: print 'Dome is already moving' return self.domeIsMoving = True # turn off Peltiers self.peltiers_onoff.turnOff() start_new_thread(self.blink, (), queue=self.queue, name='Blinking') the_file_root = 'DomeOpening' if writelog: log('time,position,speed,v1,v2,c1,c2,elapsed_time\n', header=True, fileroot=the_file_root) time.sleep(6.4) #self.ps1.turnOff() self.motor_onoff.turnOff() # put motor in reverse direction self.motor_dir.turnOn() # turn on power supply self.ps1.setVoltage(24) self.ps1.setCurrent(20) t0 = datetime.datetime.utcnow() t1 = datetime.datetime.utcnow() elapsedTime = (t1-t0).total_seconds() self.get_encoder(verbose=False) self.motor_onoff.turnOn() while (not self.dome_open.isPushed() and elapsedTime < timeout and \ abs(self.domePosition - cfg.OpenDomePosition) > 0.2): self.peltiers_onoff.turnOff() t1 = datetime.datetime.utcnow() elapsedTime = (t1-t0).total_seconds() self.get_encoder(verbose=False) if writelog: ## t1 does not have the format .strftime('%Y-%m-%d %H:%M:%S.%f') ## if not needed, i prefer not to change it: a bug here could result in catastrophic failure bla = [t1, self.domePosition, self.deltacoder, self.ps1.getVoltage(), self.ps2.getVoltage(), self.ps1.getCurrent(), self.ps2.getCurrent(), elapsedTime] log(','.join(map(str, bla))+'\n', fileroot=the_file_root) if abs(self.domePosition - cfg.OpenDomePosition) < 10: self.ps1.setVoltage(10.+14./10.*abs(self.domePosition - cfg.OpenDomePosition)) self.motor_onoff.turnOff() self.deltacoder = 0 self.ps1.setVoltage(20) time.sleep(1) self.domeIsMoving = False if writelog: ## t1 does not have the format .strftime('%Y-%m-%d %H:%M:%S.%f') ## if not needed, i prefer not to change it: a bug here could result in catastrophic failure bla = [t1, self.domePosition, self.deltacoder, self.ps1.getVoltage(), self.ps2.getVoltage(), self.ps1.getCurrent(), self.ps2.getCurrent(), nan] log(','.join(map(str, bla))+'\n', fileroot=the_file_root)
[docs] def close_dome(self, timeout=25, writelog=True): ''' Closes the dome Keywords: timeout: integer (default 25 sec) writelog: boolean (default True) ''' myprint('Closing the dome', queue=self.queue, timestamp=False) if self.is_dome_closed(): print 'Dome is already closed' return if self.domeIsMoving: print 'Dome is already moving' return self.domeIsMoving = True # turn off Peltiers self.peltiers_onoff.turnOff() start_new_thread(self.blink, (), queue=self.queue, name='Blinking') the_file_root = 'DomeClosing' if writelog: log('time,position,speed,v1,v2,c1,c2,elapsed_time\n', header=True, fileroot=the_file_root) time.sleep(6.4) #self.ps1.turnOff() self.motor_onoff.turnOff() # put motor in closing direction self.motor_dir.turnOff() # turn on power supply self.ps1.setVoltage(24) self.ps1.setCurrent(20) t0 = datetime.datetime.utcnow() t1 = datetime.datetime.utcnow() elapsedTime = (t1-t0).total_seconds() self.d = 0 self.get_encoder(verbose=False) self.motor_onoff.turnOn() while (not self.dome_closed.isPushed() and elapsedTime < timeout and \ abs(self.domePosition - cfg.ClosedDomePosition) > 0.2): self.peltiers_onoff.turnOff() t1 = datetime.datetime.utcnow() elapsedTime = (t1-t0).total_seconds() self.get_encoder(verbose=False) if writelog: ## t1 does not have the format .strftime('%Y-%m-%d %H:%M:%S.%f') ## if not needed, i prefer not to change it: a bug here could ## result in catastrophic failure bla = [t1, self.domePosition, self.deltacoder, self.ps1.getVoltage(), self.ps2.getVoltage(), self.ps1.getCurrent(), self.ps2.getCurrent(), elapsedTime] log(','.join(map(str, bla))+'\n', fileroot=the_file_root) if abs(self.domePosition - cfg.ClosedDomePosition) < 10: self.ps1.setVoltage(10.+14./10.*abs(self.domePosition - cfg.ClosedDomePosition)) ## MARK DOME CLOSED AND OPEN POSITIONS !!!! self.motor_onoff.turnOff() self.deltacoder = 0 self.ps1.setVoltage(20) time.sleep(1) self.domeIsMoving = False if writelog: ## t1 does not have the format .strftime('%Y-%m-%d %H:%M:%S.%f') ## if not needed, i prefer not to change it: a bug here could ## result in catastrophic failure bla = [t1, self.domePosition, self.deltacoder, self.ps1.getVoltage(), self.ps2.getVoltage(), self.ps1.getCurrent(), self.ps2.getCurrent(), nan] log(','.join(map(str, bla))+'\n', fileroot=the_file_root)
## Turning LEDS on/off
[docs] def turn_led_on(self, *args): the_led = args[0].upper() if len(args) > 0 else 'N' if not the_led in ['N', 'E', 'S', 'W', 'C']: myprint('Argument can only be N, E, S, W or C', error=True, queue=self.queue) return myprint('Turning LED {0} on'.format(the_led), queue=self.queue) self.leds[the_led].turnOn()
[docs] def turn_led_off(self, *args): the_led = args[0].upper() if len(args) > 0 else 'N' if not the_led in ['N', 'E', 'S', 'W', 'C']: myprint('Argument can only be N, E, S, W or C', error=True, queue=self.queue) return myprint('Turning LED {0} off'.format(the_led), queue=self.queue) self.leds[the_led].turnOff()
## Turning window heaters on/off
[docs] def turn_heater_on(self): self.heater.turnOn()
[docs] def turn_heater_off(self): self.heater.turnOff()
## Turning cameras on/off
[docs] def turn_camera_on(self, *args): the_camera = args[0].upper() if len(args) > 0 else 'N' if not the_camera in ['N', 'E', 'S', 'W', 'C']: myprint('Argument can only be N, E, S, W or C', error=True, queue=self.queue) return myprint('Turning camera {0} on'.format(the_camera), queue=self.queue) if the_camera == 'N': self.turn_pdu_on(cfg.outletCameraN) elif the_camera == 'E': self.turn_pdu_on(cfg.outletCameraE) elif the_camera == 'S': self.turn_pdu_on(cfg.outletCameraS) elif the_camera == 'W': self.turn_pdu_on(cfg.outletCameraW) elif the_camera == 'C': self.turn_pdu_on(cfg.outletCameraC)
[docs] def turn_camera_off(self, *args): the_camera = args[0].upper() if len(args) > 0 else 'N' if not the_camera in ['N', 'E', 'S', 'W', 'C']: myprint('Argument can only be N, E, S, W or C', error=True, queue=self.queue) return myprint('Turning camera {0} off'.format(the_camera), queue=self.queue) if the_camera == 'N': self.turn_pdu_off(cfg.outletCameraN) elif the_camera == 'E': self.turn_pdu_off(cfg.outletCameraE) elif the_camera == 'S': self.turn_pdu_off(cfg.outletCameraS) elif the_camera == 'W': self.turn_pdu_off(cfg.outletCameraW) elif the_camera == 'C': self.turn_pdu_off(cfg.outletCameraC)
[docs] def turn_all_cameras_on(self): myprint('Turning all cameras on', queue=self.queue) self.turn_pdu_on(cfg.outletCameraN) self.turn_pdu_on(cfg.outletCameraE) self.turn_pdu_on(cfg.outletCameraS) self.turn_pdu_on(cfg.outletCameraW) self.turn_pdu_on(cfg.outletCameraC)
[docs] def turn_all_cameras_off(self): myprint('Turning all cameras off', queue=self.queue) self.turn_pdu_off(cfg.outletCameraN) self.turn_pdu_off(cfg.outletCameraE) self.turn_pdu_off(cfg.outletCameraS) self.turn_pdu_off(cfg.outletCameraW) self.turn_pdu_off(cfg.outletCameraC)
## Turning Peltiers on/off
[docs] def turn_peltiers_on(self): self.peltiers_onoff.turnOn()
[docs] def turn_peltiers_off(self): self.peltiers_onoff.turnOff()
[docs] def set_peltiers_to_cool(self): self.peltiers_dir.turnOff()
[docs] def set_peltiers_to_heat(self): self.peltiers_dir.turnOn()
## Turning power supplies on/off
[docs] def turn_power_on(self): self.ps1.turnOn()
[docs] def turn_power_off(self): self.ps1.turnOff()
[docs] def set_voltage(self, *args): V = int(args[0]) if len(args) > 0 else 20 myprint('Setting voltage to {0}'.format(V), queue=self.queue) self.ps1.setVoltage(V)
[docs] def set_current(self, *args): C = int(args[0]) if len(args) > 0 else 20 myprint('Setting current to {0}'.format(C), queue=self.queue) self.ps1.setCurrent(C)
## Turning pdu's on/off
[docs] def turn_pdu_on(self, outlet): with self.pdu_lock: outlet = int(outlet) pdu = (outlet-1) // 8 outlet = (outlet-1) % 8 + 1 if pdu == 0: self.pdu_station.turn_on(outlet) elif pdu == 1: self.pdu_computers.turn_on(outlet)
[docs] def turn_pdu_off(self, outlet): with self.pdu_lock: outlet = int(outlet) pdu = (outlet-1) // 8 outlet = (outlet-1) % 8 + 1 if pdu == 0: self.pdu_station.turn_off(outlet) elif pdu == 1: self.pdu_computers.turn_off(outlet)
[docs] def reboot_pdu(self, outlet): with self.pdu_lock: outlet = int(outlet) pdu = (outlet-1) // 8 outlet = (outlet-1) % 8 + 1 if pdu == 0: self.pdu_station.reboot(outlet) elif pdu == 1: self.pdu_computers.reboot(outlet)
## Get pdu status
[docs] def get_pdu_status(self, arr=False): '''Gets status from the PDUs and transforms them in 2 x 8-bit integers ''' def status2bool(status): status = status.split(':')[1].strip() if status.lower() == 'on': return 1 if status.lower() == 'off': return 0 return -1 def pdustatus(pdu, arr=False): st = pdu.get_status() if arr: return st ## transforms status array into bit array (1 = ON, 0 = OFF) st = map(status2bool, st) out = 0 for b in reversed(st): if b == -1: return -1 out = (out << 1) | b return out with self.pdu_lock: if arr: ## returns array of on/off values out = (pdustatus(self.pdu_station, arr=True), pdustatus(self.pdu_computers, arr=True)) else: out = (pdustatus(self.pdu_station), pdustatus(self.pdu_computers)) return out
[docs] def get_pdu_power(self): '''Gets power from the PDUs ''' with self.pdu_lock: out = (self.pdu_station.get_power(), self.pdu_computers.get_power()) return out
[docs] def get_pdu_current(self): '''Gets power from the PDUs ''' with self.pdu_lock: out = (self.pdu_station.get_current(), self.pdu_computers.get_current()) return out
[docs] def take_rh_measurements(self): ''' Takes measures of the humidity sensors and their status. ''' from numpy import append # a. Get local time date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') subindex = ['Temp', 'Hum', 'Status'] tmpall = [] names = [] with self.rh_lock: for d in self.rhsensors.itervalues(): names.append(d.name) if self.is_shut_down: return tmp = d.getAll() self.rh_measurements[d.name]['Temp'] = append(self.rh_measurements[d.name]['Temp'],\ (float(tmp[0]) if tmp[0] else 0)) if len(self.rh_measurements[d.name]['Temp']) > 5: self.rh_measurements[d.name]['Temp'] = self.rh_measurements[d.name]['Temp'][1:] self.rh_measurements[d.name]['Hum'] = append(self.rh_measurements[d.name]['Hum'], \ (float(tmp[1]) if tmp[1] else 0)) if len(self.rh_measurements[d.name]['Hum']) > 5: self.rh_measurements[d.name]['Hum'] = self.rh_measurements[d.name]['Hum'][1:] self.rh_measurements[d.name]['Status'] = append(self.rh_measurements[d.name]['Status'], tmp[2]) if len(self.rh_measurements[d.name]['Status']) > 5: self.rh_measurements[d.name]['Status'] = self.rh_measurements[d.name]['Status'][1:] tmpall.append(tmp) tmp = [item for sublist in tmpall for item in sublist] ## write header line in csv file the_file_root = 'HumTemp' titleStr = ', '.join(j+' '+k for k in names for j in subindex) log('### '+titleStr+'\n', header=True, fileroot=the_file_root) try: astr = '{0},{1}'.format(date, ','.join(tmp))+'\n' log(astr, fileroot=the_file_root) except: print 'ERROR: ', tmp
[docs] def get_weather(self, verbose=True): ''' Gets the latest reading from the weather station. ''' if not 'skytemp' in self.weather: self.weather['skytemp'] = [0, 0, 0] with self.weather_lock: try: from paramiko import SSHClient, AutoAddPolicy swasp = SSHClient() swasp.set_missing_host_key_policy(AutoAddPolicy()) swasp.connect(cfg.ipWeather, username=creds.weather_username, password=creds.weather_password) ### 1. Get the latest weather log from SWASP _, stdout, _ = swasp.exec_command('tail -1 /tmp/weather.log', bufsize=1096) weatherlog = stdout.read() swasp.close() except: self.weather['windspeed'] = 0 self.weather['winddir'] = 0 self.weather['temperature'] = 0 self.weather['temperature2'] = 0 self.weather['humidity'] = 0 self.weather['humidity2'] = 0 self.weather['pressure'] = 0 self.weather['rain'] = 'RAIN' self.weather['rain2'] = 'RAIN' self.weather['skytemp'] = [0, 0, 0] self.weather['dewpt'] = 0 self.weather['wstatus'] = 0 self.weather['error'] = str(traceback.format_exc()) return #with open('weather.log', 'rb') as f: # weatherlog=f.read() # 2. Now get and interpret the entry weatherlog = re.split(' +', weatherlog) weatherlog = [line for line in weatherlog] self.weather['windspeed'] = float(weatherlog[1]) self.weather['winddir'] = float(weatherlog[2]) self.weather['temperature'] = float(weatherlog[11]) self.weather['temperature2'] = float(weatherlog[3]) self.weather['humidity'] = float(weatherlog[14]) self.weather['humidity2'] = float(weatherlog[4]) self.weather['pressure'] = float(weatherlog[5]) self.weather['rain'] = weatherlog[7] self.weather['rain2'] = weatherlog[9] self.weather['skytemp'] = self.weather['skytemp'][1:] + [float(weatherlog[8])] self.weather['dewpt'] = float(weatherlog[-1]) self.weather['wstatus'] = 1 self.weather['error'] = '' if verbose: text = ' Weather:\n' text += ' - Wind speed: {0} km/h\n'.format(self.weather['windspeed']) text += ' - Wind direction: {0} degrees\n'.format(self.weather['winddir']) text += ' - Pressure: {0} mbar\n'.format(self.weather['pressure']) text += ' - Temperature : {0}, {1} C\n'.format(self.weather['temperature'], self.weather['temperature2']) text += ' - Humidity : {0}, {1} C\n'.format(self.weather['humidity'], self.weather['humidity2']) text += ' - Dew Point : {0} C\n'.format(self.weather['dewpt']) text += ' - Sky Temperature : {0} C\n'.format(self.weather['skytemp'][-1]) text += ' - {0}, {1}\n\n'.format(self.weather['rain'], self.weather['rain2']) myprint(text, queue=self.queue, timestamp=False)
[docs] def get_status(self, verbose=True, writelog=True): '''Checks for all possible (known) problems and returns a code representing the status ''' status = 0 self.get_weather(verbose=verbose) self.get_encoder(verbose=False) isOpen = self.is_dome_open(updateDomePosition=False) isClosed = self.is_dome_closed(updateDomePosition=False) if isOpen: status += errs.STATUS_DOME_OPEN.code if (not isOpen)*(not isClosed)*(not self.domeIsMoving): status += errs.ERROR_DOME_POSITION.code if (self.dome_open.isPushed())*(self.dome_closed.isPushed()): status += errs.ERROR_LIMIT_SWITCHES.code if self.is_door_open(): status += errs.WARNING_DOOR_OPEN.code if 'enclosure' in self.rh_measurements and (self.rh_measurements['enclosure']['Temp'] > cfg.TempInEnclosureThreshold).all(): status += errs.ERROR_ENCLOSURE_TEMPERATURE.code if 'enclosure' in self.rh_measurements and (self.rh_measurements['enclosure']['Hum'][-1] > cfg.HumInEnclosureThreshold): status += errs.ERROR_ENCLOSURE_HUMIDITY.code if ('dome' in self.rh_measurements and (self.rh_measurements['dome']['Hum'][-1] > cfg.HumInDomeThreshold)) or ('electronics_box' in self.rh_measurements and (self.rh_measurements['electronics_box']['Hum'][-1] > cfg.HumInDomeThreshold)): status += errs.ERROR_DOME_HUMIDITY.code if (self.weather['humidity'] > cfg.HumOutsideThreshold) or (self.weather['humidity2'] > cfg.HumOutsideThreshold): status += errs.ALERT_OUTSIDE_HUMIDITY.code if (self.weather['temperature'] < cfg.TempOutsideThreshold) or (self.weather['temperature2'] < cfg.TempOutsideThreshold): status += errs.ALERT_OUTSIDE_TEMPERATURE.code if 'enclosure' in self.rh_measurements and all([s != 'OK' for s in self.rh_measurements['enclosure']['Status']]): status += errs.ERROR_COM_SENSOR_ENCLOSURE.code if 'electronics_box' in self.rh_measurements and all([s != 'OK' for s in self.rh_measurements['electronics_box']['Status']]): status += errs.ERROR_COM_SENSOR_ELECBOX.code if 'dome' in self.rh_measurements and all([s != 'OK' for s in self.rh_measurements['dome']['Status']]): status += errs.ERROR_COM_SENSOR_DOME.code if self.weather['windspeed'] > cfg.windthreshold: status += errs.ALERT_WIND.code #if self.weather['rain'] == 'RAIN' or self.weather['rain2'] == 'RAIN': # status += errs.ALERT_RAIN.code if self.weather['rain'] == 'CLOUD' or min(self.weather['skytemp']) > cfg.SkyTempThreshold: status += errs.ALERT_SKYTEMP.code if self.daq.status != 0: status += errs.ERROR_COM_DAQ.code if not self.weather['wstatus']: myprint('Communication with weather station failed', queue=self.queue) status += errs.ERROR_COM_WEATHERSTATION.code v1, v2 = self.ps1.getVoltage(), self.ps2.getVoltage() c1, c2 = self.ps1.getCurrent(), self.ps2.getCurrent() if v1 < 3 or v2 < 3: status += errs.ERROR_POWER_SUPPLIES.code v1 = int(v1*10)/10. v2 = int(v2*10)/10. c1 = int(c1*10)/10. c2 = int(c2*10)/10. if verbose: myprint(str(status), queue=self.queue, timestamp=False) self.status = status ## resetting station.time_of_action (last time an error occurred) for err in errs.ALL_ERRORS: if (self.status & err.code) and err.actions: self.time_of_action = datetime.datetime.utcnow() ## log status if writelog: the_file_root = 'Status' log('### time, status, v1, c1, v2, c2, onoff, encoder\n', header=True, fileroot=the_file_root) timenow = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') astr = ','.join('{'+str(i)+'}' for i in xrange(8)).format(timenow, self.status, v1, c1, v2, c2, self.onoff_code, int(self.domePosition*100)/100.) log(astr+'\n', fileroot=the_file_root)
@property def onoff_code(self): astr = '' for led in self.leds.itervalues(): astr += str(int(led.isOn())) astr += str(int(self.heater.isOn())) astr += str(int(self.dome_open.isPushed())) astr += str(int(self.dome_closed.isPushed())) astr += str(int(self.door.isPushed())) astr += str(int(self.peltiers_onoff.isOn())) astr += str(int(self.peltiers_dir.isOn())) return int(astr, base=2)
[docs] def log_pdu_power(self): the_file_root = 'PDU_Power' pdu_station_power, pdu_computers_power = self.get_pdu_power() res = re.search('(\d+) VA', pdu_station_power) if res: pdu_station_power = res.group(1) res = re.search('(\d+) VA', pdu_computers_power) if res: pdu_computers_power = res.group(1) log('### time, pdu_station_power, pdu_computers_power\n', header=True, fileroot=the_file_root) astr = '{0},{1},{2}'.format(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'), pdu_station_power, pdu_computers_power,) log(astr+'\n', fileroot=the_file_root)
[docs] def log_weather(self): ## write weather log file the_file_root = 'Weather' titleStr = ', '.join(self.weather.iterkeys()) log('### time, '+titleStr+'\n', header=True, fileroot=the_file_root) weather = self.weather.copy() weather['skytemp'] = weather['skytemp'][-1] astr = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') astr += ',{0}'.format(', '.join(map(str, weather.itervalues())))+'\n' log(astr, fileroot=the_file_root)
[docs] def log_status(self): '''logs all entries for plotting purposes ''' v1, v2 = self.ps1.getVoltage(), self.ps2.getVoltage() c1, c2 = self.ps1.getCurrent(), self.ps2.getCurrent() v1 = int(v1*10)/10. v2 = int(v2*10)/10. c1 = int(c1*10)/10. c2 = int(c2*10)/10. pdu1, pdu2 = self.get_pdu_status() the_file_root = 'StatusLastDay' dictnames = {'Hum':'h', 'Temp':'t', 'Status':'s', 'enclosure':'enc', 'electronics_box':'elb', 'dome':'dom'} rh_fields = ', '.join(dictnames[j]+'_'+dictnames[i] for i, v in self.rh_measurements.iteritems() for j in v) rh_vals = ','.join(str(v[j][-1]) for i, v in self.rh_measurements.iteritems() for j in v) weather = self.weather.copy() weather['skytemp'] = weather['skytemp'][-1] del weather['error'] weather_fields = ', '.join(weather.keys()) weather_vals = ','.join(map(str, weather.values())) #led_vals = ''.join(str(int(v.isOn())) for v in self.leds.itervalues()) rollinglog('### time, status, pdu1, pdu2, v1, c1, v2, c2, ' \ +rh_fields+', '+weather_fields+', encoder\n', header=True, fileroot=the_file_root) astr = ','.join('{'+str(i)+'}' for i in range(11)).format(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'), \ self.status, pdu1, pdu2, v1, c1, v2, c2, rh_vals, weather_vals, int(self.domePosition*100)/100.) rollinglog(astr+'\n', fileroot=the_file_root, nlines=1440)
[docs] def get_encoder(self, verbose=True, init=False): mturn, sturn = self.encoder.ConverseWithEncoder() current_pos = mturn+float(sturn)/360 ## take a second measurement if reading is off if current_pos < cfg.ClosedDomePosition - 5 or current_pos > cfg.OpenDomePosition + 5: mturn, sturn = self.encoder.ConverseWithEncoder() current_pos = mturn+float(sturn)/360 current_pos = round(current_pos*100)/100. ## looks like deltacoder is no longer used if init: self.deltacoder = 0 else: previous_pos = self.domePosition self.deltacoder = abs(current_pos - previous_pos)+0.001 self.encoder_measurements.append(current_pos) self.encoder_measurements = self.encoder_measurements[-3:] self.domePosition = current_pos if verbose: myprint(current_pos, queue=self.queue, timestamp=False) return current_pos
[docs] def get_status_messages(self, verbose=True): if self.status == 0: msg = ['All is good'] else: msg = [err.msg for err in errs.ALL_ERRORS if self.status & err.code] if verbose: myprint('\n'.join(msg), queue=self.queue, timestamp=False) return msg
[docs] def text_status(self): text = 'Station status:\n\n' text += ' Power Distribution Units:\n' text += ' - PDU Station: \n{0}\n\n'.format('\n'.join(self.pdu_station.get_status())) text += ' - PDU Computers: \n{0}\n\n'.format('\n'.join(self.pdu_computers.get_status())) text += ' Power supplies:\n' text += ' - Power Supply 1: V = {0}, C = {1}\n'.format(self.ps1.getVoltage(), self.ps1.getCurrent()) text += ' - Power Supply 2: V = {0}, C = {1}\n\n'.format(self.ps2.getVoltage(), self.ps2.getCurrent()) text += ' Limit switches:\n' text += ' - DomeOpened is '+('not ' if not self.dome_open.isPushed() else '')+'pushed\n' text += ' - DomeClosed is '+('not ' if not self.dome_closed.isPushed() else '')+'pushed\n' text += ' - Door is '+('not ' if not self.door.isPushed() else '')+'pushed\n\n' text += ' Peltiers:\n' text += ' '+('Off' if self.peltiers_onoff.isOff() else ('Cooling' if self.peltiers_dir.isOff() else 'Heating'))+'\n\n' text += ' Encoder:\n' text += ' {0}\n\n'.format(self.get_encoder(verbose=False)) text += ' Humidity sensors:\n' if 'enclosure' in self.rh_measurements: text += ' - Inside Camera Enclosure: T = {0}, RH = {1}, status = {2}\n'.format(self.rh_measurements['enclosure']['Temp'], self.rh_measurements['enclosure']['Hum'], self.rh_measurements['enclosure']['Status']) if 'dome' in self.rh_measurements: text += ' - Inside MASCARA station: T = {0}, RH = {1}, status = {2}\n'.format(self.rh_measurements['dome']['Temp'], self.rh_measurements['dome']['Hum'], self.rh_measurements['dome']['Status']) if 'electronics_box' in self.rh_measurements: text += ' - Inside Electronics Box: T = {0}, RH = {1}, status = {2}\n\n'.format(self.rh_measurements['electronics_box']['Temp'], self.rh_measurements['electronics_box']['Hum'], self.rh_measurements['electronics_box']['Status']) text += ' Weather:\n' text += ' - Wind speed: {0} km/h\n'.format(self.weather['windspeed']) text += ' - Wind direction: {0} degrees\n'.format(self.weather['winddir']) text += ' - Pressure: {0} mbar\n'.format(self.weather['pressure']) text += ' - Temperature : {0}, {1} C\n'.format(self.weather['temperature'], self.weather['temperature2']) text += ' - Humidity : {0}, {1} C\n'.format(self.weather['humidity'], self.weather['humidity2']) text += ' - Dew Point : {0} C\n'.format(self.weather['dewpt']) text += ' - Sky Temperature : {0} C\n'.format(self.weather['skytemp'][-1]) text += ' - {0}, {1}\n\n'.format(self.weather['rain'], self.weather['rain2']) return text
[docs] def send_warning(self, *args, **kwargs): '''sends an email to the MASCARA team notifying possible failures of the station. ''' text = args[0] if len(args) > 0 else '' to = eval(args[1]) if len(args) > 1 else creds.recipients_error_emails ING2 = bool(args[2]) if len(args) > 2 else False nostatus = kwargs.pop('nostatus', False) myprint('Sending {0} to {1} (ING2 = {2})'.format(text, to, ING2), queue=self.queue) body = 'Hi all,\n\n' body += 'The following error occurred:\n' body += text.upper()+'\n\n' body += 'Please take adequate actions.\n\n' body += 'Best,\n' body += 'MASCARA' body += '\n\n--------------------------------------\n\n' if not nostatus: body += self.text_status() try: import smtplib from email.mime.text import MIMEText mascara_user = creds.mascara_mail_username maspwd = creds.mascara_mail_password if ING2: to.append('someone@ing.iac.es') smtpserver = smtplib.SMTP("smtp.gmail.com", 587) smtpserver.ehlo() smtpserver.starttls() smtpserver.login(mascara_user, maspwd) msg = MIMEText(body) msg['Subject'] = 'Warning from Mascara - La Palma' msg['From'] = 'MASCARA - La Palma' msg['To'] = ', '.join(to) smtpserver.sendmail(mascara_user, to, msg.as_string()) smtpserver.close() except: myprint("the following email was not sent", warning=True, queue=self.queue) myprint(body, queue=self.queue)
[docs] def cleanexit(self, turnOffStation=False): ''' Cleanexit is a tentative routine to exit the station properly: meaning closing safely all the serial port connections, saving the measurements if some were taken, etc. ''' self.is_shut_down = True myprint('Turning off the power ...', queue=self.queue) self.ps1.turnOff() myprint('Closing the connection to the humidity sensors ...', queue=self.queue) self.close_rh() myprint('Closing the encoder ...', queue=self.queue) self.encoder.closeEncoder() if turnOffStation: myprint('Turning off the station outlet on PDU ...', queue=self.queue) self.turn_pdu_off(cfg.outletStation)
[docs] def shutdown(self): myprint('Shutting down the station', queue=self.queue) self.is_shut_down = True myprint('Turning off the cameras ...', queue=self.queue) self.turn_all_cameras_off() myprint('Turning off the heat exchanger ...', queue=self.queue) self.turn_pdu_off(cfg.outletHeatExchanger) myprint('Turning off the power ...', queue=self.queue) self.ps1.turnOff() myprint('Closing the connection with the humidity sensors ...', queue=self.queue) self.close_rh() myprint('Closing the encoder ...', queue=self.queue) self.encoder.closeEncoder() myprint('Turning off the station outlet on PDU ...', queue=self.queue) self.turn_pdu_off(cfg.outletStation) #myprint('Closing the connection with the PDUs ...', queue=self.queue) #self.pdu_station.close() #self.pdu_computers.close() myprint('Station is now shut down ', queue=self.queue)
[docs] def restart(self): myprint('Restarting the station ', queue=self.queue) self.__init__(queue=self.queue)
[docs] def reboot(self): if not self.is_shut_down: myprint('Rebooting the station ', queue=self.queue) myprint('Turning off the power ...', queue=self.queue) self.ps1.turnOff() myprint('Turning off the station outlet on PDU ...', queue=self.queue) self.turn_pdu_off(cfg.outletStation) myprint('Waiting to restart ...', queue=self.queue) time.sleep(60) myprint('Turning the power back on ...', queue=self.queue) myprint('Turning on the station outlet on PDU ...', queue=self.queue) self.turn_pdu_on(cfg.outletStation) self.ps1.turnOn(V=20) myprint('Station is now rebooted', queue=self.queue)
[docs] def restart_cameras(self): if not self.is_shut_down: self.turn_all_cameras_off() time.sleep(30) self.turn_all_cameras_on()
[docs] def stop_motor(self): self.ps1.turnOff() self.motor_onoff.turnOff()