'''
Created on Jun 26, 2012

@author: manuel
'''
import numpy as np

from PhysioMonitor.TriggerPlot import TriggerPlot
from numpy.core.fromnumeric import nonzero  # @UnresolvedImport
from numpy.core.numeric import array, where
from numpy.lib.function_base import diff, append
from threading import Timer
import gst  # @UnresolvedImport
import numpy
import time

DEBUG = True


def smooth(x, window_len=11, window='hanning'):
    """smooth the data using a window with requested size.
    
    This method is based on the convolution of a scaled window with the signal.
    The signal is prepared by introducing reflected copies of the signal 
    (with the window size) in both ends so that transient parts are minimized
    in the begining and end part of the output signal.
    
    input:
        x: the input signal 
        window_len: the dimension of the smoothing window; should be an odd integer
        window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
            flat window will produce a moving average smoothing.

    output:
        the smoothed signal
        
    example:

    t=linspace(-2,2,0.1)
    x=sin(t)+randn(len(t))*0.1
    y=smooth(x)
    
    see also: 
    
    numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
    scipy.signal.lfilter
 
    TODO: the window parameter could be the window itself if an array instead of a string
    NOTE: length(output) != length(input), to correct this: return y[(window_len/2-1):-(window_len/2)] instead of just y.
    """

    if x.ndim != 1:
        raise ValueError, "smooth only accepts 1 dimension arrays."

    if x.size < window_len:
        raise ValueError, "Input vector needs to be bigger than window size."

    if window_len < 3:
        return x

    if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
        raise ValueError, "Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'"

    s = numpy.r_[x[window_len - 1:0:-1], x, x[-1:-window_len:-1]]
    # print(len(s))
    if window == 'flat':  # moving average
        w = numpy.ones(window_len, 'd')
    else:
        w = eval('numpy.' + window + '(window_len)')

    y = numpy.convolve(w / w.sum(), s, mode='valid')
    return y


def peakdet(v, delta, x=None):
    """
    Converted from MATLAB script at http://billauer.co.il/peakdet.html
    
    Returns two arrays
    
    function [maxtab, mintab]=peakdet(v, delta, x)
    %PEAKDET Detect peaks in a vector
    %        [MAXTAB, MINTAB] = PEAKDET(V, DELTA) finds the local
    %        maxima and minima ("peaks") in the vector V.
    %        MAXTAB and MINTAB consists of two columns. Column 1
    %        contains indices in V, and column 2 the found values.
    %      
    %        With [MAXTAB, MINTAB] = PEAKDET(V, DELTA, X) the indices
    %        in MAXTAB and MINTAB are replaced with the corresponding
    %        X-values.
    %
    %        A point is considered a maximum peak if it has the maximal
    %        value, and was preceded (to the left) by a value lower by
    %        DELTA.
    
    % Eli Billauer, 3.4.05 (Explicitly not copyrighted).
    % This function is released to the public domain; Any use is allowed.
    
    """
    maxtab = []
    mintab = []

    if x is None:
        x = numpy.arange(len(v))

    v = numpy.asarray(v)

    if len(v) != len(x):
        raise TypeError('Input vectors v and x must have same length')

    if not numpy.isscalar(delta):
        raise TypeError('Input argument delta must be a scalar')

    if delta <= 0:
        raise TypeError('Input argument delta must be positive')

    mn, mx = numpy.Inf, -numpy.Inf
    mnpos, mxpos = numpy.NaN, numpy.NaN

    lookformax = True

    for i in numpy.arange(len(v)):
        this = v[i]
        if this > mx:
            mx = this
            mxpos = x[i]
        if this < mn:
            mn = this
            mnpos = x[i]

        if lookformax:
            if this < mx - delta:
                maxtab.append((mxpos, mx))
                mn = this
                mnpos = x[i]
                lookformax = False
        else:
            if this > mn + delta:
                mintab.append((mnpos, mn))
                mx = this
                mxpos = x[i]
                lookformax = True

    return array(maxtab), array(mintab)


###
# TrendPlot is capable of performing various measurements on the content of the plot
# each measurement is implemented by a function of the form:
#     (ret [float],units [string]) = doMeasurement(data [array], *args)

def doGetHR(inData, *args):
    '''
    returns the average heart rate over the duration of the data passed to the
    function
    The measurement is performed based (loosely) based on the algorithm
    described in <<<need to find ref>>>
    ---
    parameters
    [0] required - the sampling rate of the signal
    [1] optional - starting point for the measurement
    ---
    this function returns a value in BPM
    '''
    args = args[0]
    # if DEBUG: print 'in doGetHR(%s,%s)'%(inData,args)
    if len(args) < 1:
        raise TypeError('required argument "sample rate" (pos 1) is missing')

    sampleFreq = args[0]
    startPoint = 0
    if len(args) >= 2:
        startPoint = args[1]

    diffData = numpy.diff(inData[startPoint:])
    sqData = numpy.power(diffData, 2)
    smoothData = smooth(sqData)

    # use RMS as a delta value for peak detection
    delta = numpy.sqrt(numpy.mean(numpy.power(smoothData, 2)))
    maxData, _ = peakdet(smoothData, delta)

    deltas = diff(maxData[:, 0])
    freqs = float(sampleFreq) / deltas
    ret = freqs.mean()
    return ret * 60.0, 'bpm'


def doGetAVG(inData, *args):
    '''
    returns the average value of the traces
    ---
    parameters:
    [0] (optional)
        the start position to take the average from
        (defaults to 0, ie the beginning of the array)
    [1] (optional)
        the number of points to consider
        (defaults to the length of the array)
    ---
    WARNING: this function does not return a unit
    '''
    args = args[0]

    startPos = 0
    N = len(inData)
    if len(args) >= 1 and args[0] is not None:
        startPos = args[0]
    if len(args) >= 2 and args[1] is not None:
        N = args[1]
    avg = np.mean(array(inData)[startPos:N])
    # if DEBUG: print 'in doGetAVG(%s,%s)->%f' % (inData, args, avg)
    return avg, ''


def doGetFREQ(inData, *args):
    '''
    returns the frequency in Hertz of the threshold crossing in the signal
    parameters:
    [0] (REQUIRED) the sampling rate of the signal
    [1] (REQUIRED) threshold value
    [2] refractory period (in seconds) -- if 2 crossings are detected during
        this period, only the first one registers
    '''
    args = args[0]
    # if DEBUG: print 'in doGetFREQ(%s,%s)'%(inData,args)
    if len(args) < 1:
        raise TypeError('required argument "sample rate" (pos 1) is missing')
    elif len(args) < 2:
        raise TypeError('required argument "threshold" (pos 2) is missing')

    sampleFreq = args[0]
    threshold = args[1]
    refractPeriod = None
    if len(args) >= 3:
        refractPeriod = round(args[2] * sampleFreq)

    # threshold crossing detection magic from Bartosz Telenczuk   (b.telenczuk@...de)
    # http://www.digipedia.pl/usenet/thread/16072/232/
    i, = nonzero((inData[:-1] < threshold) & (inData[1:] > threshold))
    # if DEBUG: print 'found %d times: %s...'%(len(times),(xData[i])[:3])

    # remove the points that are in the refractory period
    j, = where(diff(i) < refractPeriod)
    j += 1
    i = numpy.delete(i, j)

    deltas = diff(i)
    freqs = float(sampleFreq) / deltas
    ret = freqs.mean()

    # if DEBUG: print 'freq array: %s'%(freqs)
    # return the average value but ignore the Inf that can pop up in the array
    #    masked = numpy.ma.masked_invalid(freqs)
    #    if masked.count()>0:
    #        ret = masked.mean()
    #    else:
    #        ret = numpy.NaN
    return ret, 'Hz'


def doGetBPM(inData, *args):
    '''
    returns the frequency in events per minutes of the threshold crossing in the signal
    parameters:
    [0] (REQUIRED) the sampling rate of the signal
    [1] (REQUIRED) threshold value
    [2] refractory period (in seconds) -- if 2 crossings are detected during
        this period, only the first one registers
    '''
    args = args[0]
    # if DEBUG: print 'in doGetBPM(%s,%s)'%(inData,args)
    ret, _ = doGetFREQ(inData, args)
    return ret * 60.0, 'bpm'


def doGetMAX(inData, *args):
    '''
    returns the max value of the data
    ---
    WARNING: this function does not return a unit
    '''
    args = args[0]
    # if DEBUG: print 'in doGetMAX(%s,%s)'%(inData,args)
    ret = inData.max()
    return ret, ''


def doGetLastPeakValue(inData, *args):
    '''
    returns the value of the last peak detected in the data
    parameters:
    [0] (REQUIRED) a delta value used to detect peaks
    [1] optional - starting point for the measurement
    ---
    WARNING: this function does not return a unit
    '''
    args = args[0]
    delta = args[0]
    startPoint = 0
    if len(args) >= 2:
        startPoint = args[1]
    maxtab, _ = peakdet(inData[startPoint:], delta)
    retVal = 0.0
    if len(maxtab) > 0:
        retVal = maxtab[-1, 1]
    return retVal, ''


class TrendPlot(TriggerPlot):
    '''
    TrendPlot is a subclass of TriggerPlot, which does the same thing, but in
    addition, takes a measurement on the trace and plots this measure over
    time over the trace
    '''

    def __init__(self, fig, rect,
                 axisbg=None,  # defaults to rc axes.facecolor
                 frameon=True,
                 sharex=None,  # use Axes instance's xaxis info
                 sharey=None,  # use Axes instance's yaxis info
                 label='',
                 xscale=None,
                 yscale=None,
                 linewidth=2.0,
                 linecolor='b',
                 sampleFreq = 1000.):
        '''
        Constructor
        '''
        # Parent constructor
        TriggerPlot.__init__(self, fig, rect, axisbg, frameon, sharex, sharey, label, xscale, yscale, linewidth,
                             linecolor, sampleFreq)
        self.set_frame_on(False)
        self.yaxis.tick_left()

        self.__trendPlot = None
        self._measurementFunc = None
        self._measurementParameters = []
        self.__trendData = None  # this will be initialized in __initialize
        self.__trendTimes = None  # this will be initialized in __initialize
        self._measurementUnits = ''
        self._measurementFormat = '%.1f'
        self._trendWidth = 30.0 * 60.0
        self._timerPeriod = 5.0
        self.__lastTimer = 0.0
        self._trendColor = 'r'
        self._trendLinewidth = 2.0

        # alarm
        self._alarmEnabled = False
        self._alarmHigh = 0.0
        self._alarmLow = 0.0
        self._alarmMuted = False
        self._alarmTripped = False
        self._alarmPitch = 1000
        self._beepDuration = 0.2
        self._beepPeriod = 1.0
        self.__alarmBgColor = (1, 1, 0.75)
        self.__normalBgColor = (1, 1, 1)
        self.__alarmTitleColor = (1, 0, 0)
        self.__normalTitleColor = (0, 0, 0)
        self.__alarmTimer = None
        self.__alarmPipeline = gst.Pipeline("mypipeline")
        self.__alarmSound = gst.element_factory_make("audiotestsrc", "audio")
        self.__alarmPipeline.add(self.__alarmSound)
        self.__alarmSink = gst.element_factory_make("alsasink", "__alarmSink")
        self.__alarmPipeline.add(self.__alarmSink)
        self.__alarmSound.link(self.__alarmSink)
        self.__alarmSound.set_property("freq", self._alarmPitch)
        self.__originalTitle = None
        self.__mutedSuffix = " (mute)"

        # creates a separate axes, with the same dimensions as the original one
        self.__trendPlot = self.figure.add_axes(rect)
        self.__trendPlot.yaxis.tick_right()
        self.__trendPlot.yaxis.set_label_position('right')
        self.__trendPlot.yaxis.set_offset_position('right')
        self.__trendPlot.xaxis.tick_top()
        self.__trendPlot.xaxis.set_label_position('top')
        self.__trendPlot.xaxis.set_visible(False)
        self.__trendPlot.set_frame_on(True)

        # initialize
        self.__trendData = array([])
        self.__trendTimes = array([])

        self.__trendPlot.plot([], '-', color=self._trendColor, linewidth=self._trendLinewidth)
        self.__trendPlot.set_axis_bgcolor(self.__normalBgColor)
        self.title.set_color(self.__normalTitleColor)
        self.__trendPlot.set_autoscaley_on(True)
        self.__measureLabel = self.text(0.95, 0.05, '', ha='right', va='bottom', size='xx-large', weight='heavy',
                                        transform=self.transAxes)
        self.__trendLabel = self.text(0.0, 1.0, '', color=self._trendColor, ha='left', va='bottom', size='x-small',
                                      transform=self.transAxes)
        self.__trendLabel.set_text('%s min @ %s s' % (self._trendWidth / 60.0, self._timerPeriod))
        self.__trendPlot.set_xlim(left=-self._trendWidth, right=0.0)
        self.__trendPlot.tick_params(axis='both', color=self._trendColor, labelcolor=self._trendColor)
        self.__trendPlot.tick_params(axis='x', labelsize='xx-small')
        # lines for the alarm boundaries
        self.__alarmLowLine = self.__trendPlot.axhline(self._alarmLow, ls='--', color=self._trendColor, visible=False)
        self.__alarmHighLine = self.__trendPlot.axhline(self._alarmHigh, ls='--', color=self._trendColor, visible=False)

        f = self.get_figure()
        f.canvas.mpl_connect('pick_event', self.onTitlePick)

    def reset(self):
        TriggerPlot.reset(self)
        self.__trendData = array([])
        self.__trendTimes = array([])
        self.__trendPlot.plot([], '-', color=self._trendColor, linewidth=self._trendLinewidth)

    def onTitlePick(self, event):
        if event.artist is self.title:
            # this first test is required because the canvas is receiving events from
            # all the elements of the figure, not just this object instance.
            if self.isAlarmMuted():
                self.unmuteAlarm()
            else:
                self.muteAlarm()

    def getBeepDuration(self):
        return self._beepDuration

    def getBeepFreq(self):
        return self._beepFreq

    def setBeepDuration(self, value):
        self._beepDuration = value

    def setBeepFreq(self, value):
        self._beepFreq = value

    def isAlarmEnabled(self):
        """
        returns whether the alarm is enabled or not
        """
        return self._alarmEnabled

    def getAlarmHigh(self):
        """
        the top value above which the alarm is tripped
        """
        return self._alarmHigh

    def getAlarmLow(self):
        """
        the bottom value under which the alarm is tripped
        """
        return self._alarmLow

    def isAlarmMuted(self):
        """
        returns whether the alarm is mutted or not
        """
        return self._alarmMuted

    def isAlarmTripped(self):
        """
        returns whether the alarm has been tripped
        """
        return self._alarmTripped

    def getAlarmPitch(self):
        return self._alarmPitch

    def setAlarmEnabled(self, value):
        self._alarmEnabled = value
        # show or hide the alarm boundaries depending on whether alarm is enabled or not
        self.__alarmHighLine.set_visible(self._alarmEnabled)
        self.__alarmLowLine.set_visible(self._alarmEnabled)

    def enableAlarm(self):
        self.setAlarmEnabled(True)

    def disableAlarm(self):
        self.setAlarmEnabled(False)

    def setAlarmHigh(self, value):
        self._alarmHigh = value
        self.__alarmHighLine.set_ydata([self._alarmHigh, self._alarmHigh])

    def setAlarmLow(self, value):
        self._alarmLow = value
        self.__alarmLowLine.set_ydata([self._alarmLow, self._alarmLow])

    def setAlarmMuted(self, value):
        self._alarmMuted = value

    def setAlarmTripped(self, value):
        self._alarmTripped = value

    def setAlarmPitch(self, value):
        self._alarmPitch = value
        self.__alarmSound.set_property("freq", self._alarmPitch)

    def getTrendColor(self):
        '''
        color of the line used to display the trend plot
        '''
        return self._trendColor

    def getTrendLinewidth(self):
        '''
        pen width of the line used to diplay the trend plot
        '''
        return self._trendLinewidth

    def setTrendColor(self, value):
        self._trendColor = value
        self.__trendPlot.lines[0].set_color(self._trendColor)
        self.__trendPlot.tick_params(axis='both', color=self._trendColor, labelcolor=self._trendColor)
        self.__trendLabel.set_color(self._trendColor)
        self.__alarmHighLine.set_color(self._trendColor)
        self.__alarmLowLine.set_color(self._trendColor)

    def setTrendLinewidth(self, value):
        self.__trendLinewidth = value
        self.__trendPlot.lines[0].set_lw(self.__trendLinewidth)

    def set_trend_ylim(self, bottom=None, top=None):
        if bottom is not None and top is not None:
            self.__trendPlot.set_autoscaley_on(False)
        self.__trendPlot.set_ylim(bottom=bottom, top=top)

    def get_trend_ylim(self):
        return self.__trendPlot.get_ylim()

    def getTimerPeriod(self):
        '''
        the period over which the measurements are averaged and plotted 
        '''
        return self._timerPeriod

    def setTimerPeriod(self, value):
        self._timerPeriod = value
        self.__trendLabel.set_text('%s min @ %s s' % (self._trendWidth / 60.0, self._timerPeriod))

    def getTrendWidth(self):
        '''
        the duration (in seconds) over which to show the evolution of the
        measured value in the TrendPlot
        '''
        return self._trendWidth

    def setTrendWidth(self, value):
        self._trendWidth = value
        self.__trendPlot.set_xlim(left=-self._trendWidth, right=0.0)
        self.__trendLabel.set_text('%s min @ %s s' % (self._trendWidth / 60.0, self._timerPeriod))

    def getMeasurementFormat(self):
        '''
        returns the measurement format used to display the value returned by the measurement function
        '''
        return self._measurementFormat

    def setMeasurementFormat(self, value):
        self._measurementFormat = value

    def getMeasurementFunc(self):
        return self._measurementFunc

    def setMeasurementFunc(self, value):
        self._measurementFunc = value

    def getMeasurementParameters(self):
        return self._measurementParameters

    def setMeasurementParameters(self, value):
        self._measurementParameters = value

    def getMeasurementUnits(self):
        """
        the units that are displayed next to the measurement
        leave blank for the default units that are provided by each measurement mode
        """
        return self._measurementUnits

    def setMeasurementUnits(self, value):
        self._measurementUnits = value

    def processNewData(self, inData):
        TriggerPlot.processNewData(self, inData)
        # if DEBUG: print 'calling _measurementFunc(%s,%s)'%(self._peekBuffer,self._measurementParameters)
        ret, units = self._measurementFunc(self._peekBuffer, self._measurementParameters)

        # if the user provided a unit for the measurement
        if (self._measurementUnits is not None and len(self._measurementUnits) > 0) or \
                        len(units) == 0:  # or if the measurement function returned no units
            # use the user-supplied units
            units = self._measurementUnits

        self.__measureLabel.set_text((self._measurementFormat + ' %s') % (ret, units))
        self.__addDataToTrendPlot(ret)

        # check for alarm conditions
        if (self.isAlarmEnabled() and not self.isAlarmTripped() and
                (ret > self.getAlarmHigh() or ret < self.getAlarmLow())
            ):
            # if alarm is enabled but has not been tripped yet, and if the
            # trend measurement is outside the boundaries, then trigger the alarm
            self.tripAlarm()

        if (self.isAlarmEnabled() and self.isAlarmTripped() and
                (ret >= self.getAlarmLow() and ret <= self.getAlarmHigh())
            ):
            # if alarm is enabled and had already been tripped
            # but the trend measurement has returned inside the boundaries
            # then reset the alarm
            self.resetAlarm()

    def tripAlarm(self):
        self.alarmTripped = True
        self.alarmMuted = False
        # change background color of the plot
        self.__trendPlot.set_axis_bgcolor(self.__alarmBgColor)
        # change plot title color
        self.title.set_color(self.__alarmTitleColor)
        self.__originalTitle = self.get_title()
        # event handling for the alarm
        self.title.set_picker(5)  ## 5 pts tolerance
        # start the sound
        self.alarmON(duration=self._beepDuration)

    def muteAlarm(self):
        # if DEBUG: print 'in muteAlarm()'
        self.alarmMuted = True
        self.__originalTitle = self.get_title()
        self.set_title(self.get_title() + self.__mutedSuffix)
        self.alarmOFF()
        if self.__alarmTimer is not None:
            self.__alarmTimer.cancel()
            self.__alarmTimer = None

    def unmuteAlarm(self):
        self.alarmMuted = False
        self.set_title(self.__originalTitle)
        if self.isAlarmEnabled() and self.isAlarmTripped():
            self.tripAlarm()

    def alarmON(self, duration=0.0):
        # if DEBUG: print 'in alarmON(%f)'%(duration)
        # if DEBUG: print 'turning alarm ON at %s'%(time.strftime("%a, %d %b %Y %H:%M:%S"))
        self.__alarmPipeline.set_state(gst.STATE_PLAYING)
        if duration > 0:
            del self.__alarmTimer
            self.__alarmTimer = Timer(duration, self.alarmOFF, [self._beepPeriod - self._beepDuration])
            # if DEBUG: print 'creating Timer object %s with interval %f'%(self.__alarmTimer.__str__(),duration)
            self.__alarmTimer.start()

    def alarmOFF(self, duration=0):
        #        if DEBUG: print 'in alarmOFF(%f)'%(duration)
        #        if DEBUG: print 'turning alarm OFF at %s'%(time.strftime("%a, %d %b %Y %H:%M:%S"))
        self.__alarmPipeline.set_state(gst.STATE_NULL)

        if duration > 0:
            del self.__alarmTimer
            self.__alarmTimer = Timer(duration, self.alarmON, [self._beepDuration])
            # if DEBUG: print 'creating Timer object %s with interval %f'%(self.__alarmTimer.__str__(),duration)
            self.__alarmTimer.start()

    def resetAlarm(self):
        #        if DEBUG: print 'in resetAlarm()'
        #        if DEBUG: print self
        self.alarmOFF()
        if self.__alarmTimer is not None:
            self.__alarmTimer.cancel()
            del self.__alarmTimer
            self.__alarmTimer = None

        self.alarmTripped = False
        self.alarmMuted = False
        # reset background color of the plot
        self.__trendPlot.set_axis_bgcolor(self.__normalBgColor)
        # reset plot title color
        self.title.set_color(self.__normalTitleColor)
        if self.__originalTitle is not None:
            self.set_title(self.__originalTitle)
        # event handling for the alarm
        self.title.set_picker(None)

    def setTriggerLevel(self, value):
        TriggerPlot.setTriggerLevel(self, value)
        if (self._measurementFunc == doGetFREQ or self._measurementFunc == doGetBPM) and \
                        len(self._measurementParameters) >= 2 and self._measurementParameters[1] is None:
            self._measurementParameters = (self._measurementParameters[0],
                                           self.triggerLevel) + \
                                          self._measurementParameters[2:]
            # if DEBUG: print 'changing threshold level in doGetFreq or doGetBPM parameters: %s'%str(self._measurementParameters)

    def __addDataToTrendPlot(self, value):
        # if DEBUG: print 'in TrendPlot.__addDataToTrendPlot(%s)'%value

        self.__trendData = append(self.__trendData, value)

        currTime = time.time()
        if currTime > (self.__lastTimer + self._timerPeriod):
            # if DEBUG: print '%d s spent since last update'%(currTime-self.__lastTimer)
            self.__trendTimes = append(self.__trendTimes, currTime)
            value = self.__trendData.mean()
            # if DEBUG: print 'adding average of %d points: %f'%(len(self.__trendData),value)
            self.__trendPlot.lines[0].set_data(
                self.__trendTimes - currTime,
                append(self.__trendPlot.lines[0].get_ydata(), value))
            self.__trendData = array([])
            self.__lastTimer = currTime

    def set_background_color(self, color):
        self.__trendPlot.patch.set_facecolor(color)

    measurementUnits = property(getMeasurementUnits, setMeasurementUnits)
    measurementParameters = property(getMeasurementParameters, setMeasurementParameters)
    measurementFunc = property(getMeasurementFunc, setMeasurementFunc)
    measurementFormat = property(getMeasurementFormat, setMeasurementFormat)
    trendWidth = property(getTrendWidth, setTrendWidth)
    timerPeriod = property(getTimerPeriod, setTimerPeriod)
    measurePeriod = property(getTimerPeriod, setTimerPeriod)
    trendColor = property(getTrendColor, setTrendColor)
    trendLinewidth = property(getTrendLinewidth, setTrendLinewidth)
    alarmEnabled = property(isAlarmEnabled, setAlarmEnabled)
    alarmHigh = property(getAlarmHigh, setAlarmHigh)
    alarmLow = property(getAlarmLow, setAlarmLow)
    alarmMuted = property(isAlarmMuted, setAlarmMuted)
    alarmTripped = property(isAlarmTripped, setAlarmTripped)
    alarmPitch = property(getAlarmPitch, setAlarmPitch)
    beepDuration = property(getBeepDuration, setBeepDuration)
    beepFreq = property(getBeepFreq, setBeepFreq)
