'''
.. 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)
[docs] def blink(self):
''' Blinks the LEDS '''
import winsound
while self.domeIsMoving:
for k in self.leds.itervalues():
k.turnOn()
winsound.Beep(500, 750)
for k in self.leds.itervalues():
k.turnOff()
time.sleep(1)
[docs] def blink_for_flats(self, the_led, nflats):
''' Turn the LEDS on and off during flats'''
if nflats <= 0:
return
on_time = cfg.expTimeFlats
off_time = cfg.expTimeObs - on_time
self.turn_led_off(the_led)
for j in xrange(nflats+7):
time.sleep(off_time/2.)
self.turn_led_on(the_led)
time.sleep(on_time)
self.turn_led_off(the_led)
time.sleep(off_time/2.)
## 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()