<commit_before>#!/usr/bin/env python
# CREATED:2013-08-22 12:20:01 by Brian McFee <brm2132@columbia.edu>
'''Music segmentation using timbre, pitch, repetition and time.
'''

import argparse
import logging
import sys

import numpy as np
import scipy.signal
import scipy.linalg

import librosa
import msaf

from msaf.algorithms.interface import SegmenterInterface

REP_WIDTH = 3
REP_FILTER = 7
N_MFCC = 32
N_CHROMA = 12
N_REP = 32

# mfcc, chroma, repetitions for each, and 4 time features
__DIMENSION = N_MFCC + N_CHROMA + 2 * N_REP + 4

def features(audio_path, annot_beats=False, pre_features=None, framesync=False):
    '''Feature-extraction for audio segmentation
    Arguments:
        audio_path -- str
        path to the input song in the Segmentation dataset

    Returns:
        - X -- ndarray

            beat-synchronous feature matrix:
            MFCC (mean-aggregated)
            Chroma (median-aggregated)
            Latent timbre repetition
            Latent chroma repetition
            Time index
            Beat index

        - beat_times -- array
            mapping of beat index => timestamp
            includes start and end markers (0, dur)

        - dur -- float
            duration of the track in seconds

    '''
    def compress_data(X, k):
        Xtemp = X.dot(X.T)
        if len(Xtemp) == 0:
            return None
        e_vals, e_vecs = np.linalg.eig(Xtemp)

        e_vals = np.maximum(0.0, np.real(e_vals))
        e_vecs = np.real(e_vecs)

        idx = np.argsort(e_vals)[::-1]

        e_vals = e_vals[idx]
        e_vecs = e_vecs[:, idx]

        # Truncate to k dimensions
        if k < len(e_vals):
            e_vals = e_vals[:k]
            e_vecs = e_vecs[:, :k]

        # Normalize by the leading singular value of X
        Z = np.sqrt(e_vals.max())

        if Z > 0:
            e_vecs = e_vecs / Z

        return e_vecs.T.dot(X)

    # Latent factor repetition features
    def repetition(X, metric='euclidean'):
        R = librosa.segment.recurrence_matrix(X,
                                            k=2 * int(np.ceil(np.sqrt(X.shape[1]))),
                                            width=REP_WIDTH,
                                            metric=metric,
                                            sym=False).astype(np.float32)

        P = scipy.signal.medfilt2d(librosa.segment.structure_feature(R), [1, REP_FILTER])

        # Discard empty rows.
        # This should give an equivalent SVD, but resolves some numerical instabilities.
        P = P[P.any(axis=1)]

        return compress_data(P, N_REP)

    #########
    #print '\tloading annotations and features of ', audio_path
    if pre_features is None:
        features = msaf.io.get_features(audio_path, annot_beats, framesync)
    else:
        features = pre_features
    chroma = features["hpcp"]
    mfcc = features["mfcc"]
    tonnetz = features["tonnetz"]
    cqt = features["cqt"]
    beats = features["beats"]
    dur = features["anal"]["dur"]

    # Sampling Rate
    sr = msaf.Anal.sample_rate

    ##########
    #print '\treading beats'
    B = beats[:chroma.shape[0]]
    #beat_frames = librosa.time_to_frames(B, sr=sr,
                                         #hop_length=msaf.Anal.hop_size)
    #print beat_frames, len(beat_frames), uidx

    #########
    M = mfcc.T
    #plt.imshow(M, interpolation="nearest", aspect="auto"); plt.show()

    #########
    # Get the beat-sync chroma
    C = chroma.T
    C += C.min() + 0.1
    C = C / C.max(axis=0)
    C = 80 * np.log10(C)  # Normalize from -80 to 0
    #plt.imshow(C, interpolation="nearest", aspect="auto"); plt.show()

    # Time-stamp features
    N = np.arange(float(chroma.shape[0]))

    #########
    #print '\tgenerating structure features'

    # TODO:
    # try:
        # This might fail if audio file (or number of beats) is too small
    R_timbre = repetition(librosa.feature.stack_memory(M))
    R_chroma = repetition(librosa.feature.stack_memory(C))
    # except:
        # return None, None, dur
    if R_timbre is None or R_chroma is None:
        return None, None, dur

    R_timbre += R_timbre.min()
    R_timbre /= R_timbre.max()
    R_chroma += R_chroma.min()
    R_chroma /= R_chroma.max()
    #plt.imshow(R_chroma, interpolation="nearest", aspect="auto"); plt.show()

    # Stack it all up
    #print M.shape, C.shape, R_timbre.shape, R_chroma.shape, len(B), len(N)
    X = np.vstack([M, C, R_timbre, R_chroma, B, B / dur, N,
                   N / float(chroma.shape[0])])

    #plt.imshow(X, interpolation="nearest", aspect="auto"); plt.show()

    return X, B, dur

def gaussian_cost(X):
    '''Return the average log-likelihood of data under a standard normal
    '''

    d, n = X.shape

    if n < 2:
        return 0

    sigma = np.var(X, axis=1, ddof=1)

    cost = -0.5 * d * n * np.log(2. * np.pi) - 0.5 * (n - 1.) * np.sum(sigma)
    return cost

def clustering_cost(X, boundaries):

    # Boundaries include beginning and end frames, so k is one less
    k = len(boundaries) - 1

    d, n = map(float, X.shape)

    # Compute the average log-likelihood of each cluster
    cost = [gaussian_cost(X[:, start:end]) for
            (start, end) in zip(boundaries[:-1], boundaries[1:])]

    cost = - 2 * np.sum(cost) / n + 2 * (d * k)

    return cost

def get_k_segments(X, k):

    # Step 1: run ward
    boundaries = librosa.segment.agglomerative(X, k)

    # Add first and last boundary indeces
    boundaries = np.unique(np.concatenate(([0], boundaries, [X.shape[1]-1])))

    # Step 2: compute cost
    cost = clustering_cost(X, boundaries)

    return boundaries, cost

def get_segments(X, kmin=8, kmax=32):

    cost_min = np.inf
    S_best = []
    best_k = -1
    for k in range(kmax, kmin, -1):
        S, cost = get_k_segments(X, k)
        if cost < cost_min:
            cost_min = cost
            S_best = S
            best_k = k
        else:
            break

    return S_best

def save_segments(outfile, S, beats):

    times = beats[S]
    with open(outfile, 'w') as f:
        for idx, (start, end) in enumerate(zip(times[:-1], times[1:]), 1):
            f.write('%.3f\t%.3f\tSeg#%03d\n' % (start, end, idx))

    pass

def process_arguments():
    parser = argparse.ArgumentParser(description='Music segmentation')

    parser.add_argument('-t',
                        '--transform',
                        dest='transform',
                        required=False,
                        type=str,
                        help='npy file containing the linear projection',
                        default=None)

    parser.add_argument('input_song',
                        action='store',
                        help='path to input audio data')

    parser.add_argument('output_file',
                        action='store',
                        help='path to output segment file')

    return vars(parser.parse_args(sys.argv[1:]))

def load_transform(transform_file):

    if transform_file is None:
        W = np.eye(__DIMENSION)
    else:
        W = np.load(transform_file)

    return W

def get_num_segs(duration, MIN_SEG=10.0, MAX_SEG=45.0):
    kmin = max(1, np.floor(duration / MAX_SEG).astype(int))
    kmax = max(2, np.ceil(duration / MIN_SEG).astype(int))

    return kmin, kmax

class Segmenter(SegmenterInterface):
    def processFlat(self):
        """Main process for flat segmentation.
        Returns
        -------
        est_idxs : np.array(N)
            Estimated times for the segment boundaries in frame indeces.
        est_labels : np.array(N-1)
            Estimated labels for the segments.
        """
        # print(self.audio_file,
              # self.annot_beats,
              # self.features,
              # self.framesync)
        # Preprocess to obtain features, times, and input boundary indeces
        F, frame_times, dur = features(self.audio_file, self.annot_beats,
                                       self.features, self.framesync)

        try:
            # Load and apply transform
            W = load_transform(self.config["transform"])
            F = W.dot(F)

            # Get Segments
            kmin, kmax = get_num_segs(dur)
            est_idxs = get_segments(F, kmin=kmin, kmax=kmax)
        except:
            # The audio file is too short, only beginning and end
            logging.warning("Audio file too short! "
                            "Only start and end boundaries.")
            est_idxs = [0, F.shape[1] - 1]

        # Make sure that the first and last boundaries are included
        assert est_idxs[0] == 0 and est_idxs[-1] == F.shape[1] - 1

        # Empty labels
        est_labels = np.ones(len(est_idxs) - 1) * -1

        # Post process estimations
        est_idxs, est_labels = self._postprocess(est_idxs, est_labels)

        return est_idxs, est_labels

    def processHierarchical(self):
        """Main process for hierarchical segmentation.
        Returns
        -------
        est_idxs : list
            List containing estimated times for each layer in the hierarchy
            as np.arrays
        est_labels : list
            List containing estimated labels for each layer in the hierarchy
            as np.arrays
        """
        # Preprocess to obtain features, times, and input boundary indeces
        F, frame_times, dur = features(self.audio_file, self.annot_beats,
                                       self.features, self.framesync)

        try:
            # Load and apply transform
            W = load_transform(self.config["transform"])
            F = W.dot(F)

            # Get Segments
            kmin, kmax = get_num_segs(dur)

            # Run algorithm layer by layer
            est_idxs = []
            est_labels = []
            for k in range(kmax, kmin, -1):
                S, cost = get_k_segments(F, k)
                est_idxs.append(S)
                est_labels.append(np.ones(len(S) - 1) * -1)

                # Make sure that the first and last boundaries are included
                assert est_idxs[-1][0] == 0 and \
                    est_idxs[-1][-1] == F.shape[1] - 1, "Layer %d does not " \
                    "start or end in the right frame(s)." % k

                # Post process layer
                est_idxs[-1], est_labels[-1] = \
                        self._postprocess(est_idxs[-1], est_labels[-1])
        except:
            # The audio file is too short, only beginning and end
            logging.warning("Audio file too short! "
                            "Only start and end boundaries.")
            est_idxs = [np.array([0, F.shape[1] - 1])]
            est_labels = [np.ones(1) * -1]

        return est_idxs, est_labels
<commit_msg>Added some docs<commit_after>#!/usr/bin/env python
# CREATED:2013-08-22 12:20:01 by Brian McFee <brm2132@columbia.edu>
'''Music segmentation using timbre, pitch, repetition and time.
'''

import argparse
import logging
import sys

import numpy as np
import scipy.signal
import scipy.linalg

import librosa
import msaf

from msaf.algorithms.interface import SegmenterInterface

REP_WIDTH = 3
REP_FILTER = 7
N_MFCC = 32
N_CHROMA = 12
N_REP = 32

# mfcc, chroma, repetitions for each, and 4 time features
__DIMENSION = N_MFCC + N_CHROMA + 2 * N_REP + 4

def features(audio_path, annot_beats=False, pre_features=None, framesync=False):
    '''Feature-extraction for audio segmentation
    Arguments:
        audio_path -- str
        path to the input song in the Segmentation dataset

    Returns:
        - X -- ndarray

            beat-synchronous feature matrix:
            MFCC (mean-aggregated)
            Chroma (median-aggregated)
            Latent timbre repetition
            Latent chroma repetition
            Time index
            Beat index

        - beat_times -- array
            mapping of beat index => timestamp
            includes start and end markers (0, dur)

        - dur -- float
            duration of the track in seconds

    '''
    def compress_data(X, k):
        Xtemp = X.dot(X.T)
        if len(Xtemp) == 0:
            return None
        e_vals, e_vecs = np.linalg.eig(Xtemp)

        e_vals = np.maximum(0.0, np.real(e_vals))
        e_vecs = np.real(e_vecs)

        idx = np.argsort(e_vals)[::-1]

        e_vals = e_vals[idx]
        e_vecs = e_vecs[:, idx]

        # Truncate to k dimensions
        if k < len(e_vals):
            e_vals = e_vals[:k]
            e_vecs = e_vecs[:, :k]

        # Normalize by the leading singular value of X
        Z = np.sqrt(e_vals.max())

        if Z > 0:
            e_vecs = e_vecs / Z

        return e_vecs.T.dot(X)

    # Latent factor repetition features
    def repetition(X, metric='euclidean'):
        R = librosa.segment.recurrence_matrix(X,
                                            k=2 * int(np.ceil(np.sqrt(X.shape[1]))),
                                            width=REP_WIDTH,
                                            metric=metric,
                                            sym=False).astype(np.float32)

        P = scipy.signal.medfilt2d(librosa.segment.structure_feature(R), [1, REP_FILTER])

        # Discard empty rows.
        # This should give an equivalent SVD, but resolves some numerical instabilities.
        P = P[P.any(axis=1)]

        return compress_data(P, N_REP)

    #########
    #print '\tloading annotations and features of ', audio_path
    if pre_features is None:
        features = msaf.io.get_features(audio_path, annot_beats, framesync)
    else:
        features = pre_features
    chroma = features["hpcp"]
    mfcc = features["mfcc"]
    tonnetz = features["tonnetz"]
    cqt = features["cqt"]
    beats = features["beats"]
    dur = features["anal"]["dur"]

    # Sampling Rate
    sr = msaf.Anal.sample_rate

    ##########
    #print '\treading beats'
    B = beats[:chroma.shape[0]]
    #beat_frames = librosa.time_to_frames(B, sr=sr,
                                         #hop_length=msaf.Anal.hop_size)
    #print beat_frames, len(beat_frames), uidx

    #########
    M = mfcc.T
    #plt.imshow(M, interpolation="nearest", aspect="auto"); plt.show()

    #########
    # Get the beat-sync chroma
    C = chroma.T
    C += C.min() + 0.1
    C = C / C.max(axis=0)
    C = 80 * np.log10(C)  # Normalize from -80 to 0
    #plt.imshow(C, interpolation="nearest", aspect="auto"); plt.show()

    # Time-stamp features
    N = np.arange(float(chroma.shape[0]))

    #########
    #print '\tgenerating structure features'

    # TODO: Handle the exceptions correctly
    # try:
        # This might fail if audio file (or number of beats) is too small
    R_timbre = repetition(librosa.feature.stack_memory(M))
    R_chroma = repetition(librosa.feature.stack_memory(C))
    # except:
        # return None, None, dur
    if R_timbre is None or R_chroma is None:
        return None, None, dur

    R_timbre += R_timbre.min()
    R_timbre /= R_timbre.max()
    R_chroma += R_chroma.min()
    R_chroma /= R_chroma.max()
    #plt.imshow(R_chroma, interpolation="nearest", aspect="auto"); plt.show()

    # Stack it all up
    #print M.shape, C.shape, R_timbre.shape, R_chroma.shape, len(B), len(N)
    X = np.vstack([M, C, R_timbre, R_chroma, B, B / dur, N,
                   N / float(chroma.shape[0])])

    #plt.imshow(X, interpolation="nearest", aspect="auto"); plt.show()

    return X, B, dur

def gaussian_cost(X):
    '''Return the average log-likelihood of data under a standard normal
    '''

    d, n = X.shape

    if n < 2:
        return 0

    sigma = np.var(X, axis=1, ddof=1)

    cost = -0.5 * d * n * np.log(2. * np.pi) - 0.5 * (n - 1.) * np.sum(sigma)
    return cost

def clustering_cost(X, boundaries):

    # Boundaries include beginning and end frames, so k is one less
    k = len(boundaries) - 1

    d, n = map(float, X.shape)

    # Compute the average log-likelihood of each cluster
    cost = [gaussian_cost(X[:, start:end]) for
            (start, end) in zip(boundaries[:-1], boundaries[1:])]

    cost = - 2 * np.sum(cost) / n + 2 * (d * k)

    return cost

def get_k_segments(X, k):

    # Step 1: run ward
    boundaries = librosa.segment.agglomerative(X, k)

    # Add first and last boundary indeces
    boundaries = np.unique(np.concatenate(([0], boundaries, [X.shape[1]-1])))

    # Step 2: compute cost
    cost = clustering_cost(X, boundaries)

    return boundaries, cost

def get_segments(X, kmin=8, kmax=32):

    cost_min = np.inf
    S_best = []
    best_k = -1
    for k in range(kmax, kmin, -1):
        S, cost = get_k_segments(X, k)
        if cost < cost_min:
            cost_min = cost
            S_best = S
            best_k = k
        else:
            break

    return S_best

def save_segments(outfile, S, beats):

    times = beats[S]
    with open(outfile, 'w') as f:
        for idx, (start, end) in enumerate(zip(times[:-1], times[1:]), 1):
            f.write('%.3f\t%.3f\tSeg#%03d\n' % (start, end, idx))

    pass

def process_arguments():
    parser = argparse.ArgumentParser(description='Music segmentation')

    parser.add_argument('-t',
                        '--transform',
                        dest='transform',
                        required=False,
                        type=str,
                        help='npy file containing the linear projection',
                        default=None)

    parser.add_argument('input_song',
                        action='store',
                        help='path to input audio data')

    parser.add_argument('output_file',
                        action='store',
                        help='path to output segment file')

    return vars(parser.parse_args(sys.argv[1:]))

def load_transform(transform_file):

    if transform_file is None:
        W = np.eye(__DIMENSION)
    else:
        W = np.load(transform_file)

    return W

def get_num_segs(duration, MIN_SEG=10.0, MAX_SEG=45.0):
    kmin = max(1, np.floor(duration / MAX_SEG).astype(int))
    kmax = max(2, np.ceil(duration / MIN_SEG).astype(int))

    return kmin, kmax

class Segmenter(SegmenterInterface):
    def processFlat(self):
        """Main process for flat segmentation.
        Returns
        -------
        est_idxs : np.array(N)
            Estimated times for the segment boundaries in frame indeces.
        est_labels : np.array(N-1)
            Estimated labels for the segments.
        """
        # print(self.audio_file,
              # self.annot_beats,
              # self.features,
              # self.framesync)
        # Preprocess to obtain features, times, and input boundary indeces
        F, frame_times, dur = features(self.audio_file, self.annot_beats,
                                       self.features, self.framesync)

        try:
            # Load and apply transform
            W = load_transform(self.config["transform"])
            F = W.dot(F)

            # Get Segments
            kmin, kmax = get_num_segs(dur)
            est_idxs = get_segments(F, kmin=kmin, kmax=kmax)
        except:
            # The audio file is too short, only beginning and end
            logging.warning("Audio file too short! "
                            "Only start and end boundaries.")
            est_idxs = [0, F.shape[1] - 1]

        # Make sure that the first and last boundaries are included
        assert est_idxs[0] == 0 and est_idxs[-1] == F.shape[1] - 1

        # Empty labels
        est_labels = np.ones(len(est_idxs) - 1) * -1

        # Post process estimations
        est_idxs, est_labels = self._postprocess(est_idxs, est_labels)

        return est_idxs, est_labels

    def processHierarchical(self):
        """Main process for hierarchical segmentation.
        Returns
        -------
        est_idxs : list
            List containing estimated times for each layer in the hierarchy
            as np.arrays
        est_labels : list
            List containing estimated labels for each layer in the hierarchy
            as np.arrays
        """
        # Preprocess to obtain features, times, and input boundary indeces
        F, frame_times, dur = features(self.audio_file, self.annot_beats,
                                       self.features, self.framesync)

        try:
            # Load and apply transform
            W = load_transform(self.config["transform"])
            F = W.dot(F)

            # Get Segments
            kmin, kmax = get_num_segs(dur)

            # Run algorithm layer by layer
            est_idxs = []
            est_labels = []
            for k in range(kmax, kmin, -1):
                S, cost = get_k_segments(F, k)
                est_idxs.append(S)
                est_labels.append(np.ones(len(S) - 1) * -1)

                # Make sure that the first and last boundaries are included
                assert est_idxs[-1][0] == 0 and \
                    est_idxs[-1][-1] == F.shape[1] - 1, "Layer %d does not " \
                    "start or end in the right frame(s)." % k

                # Post process layer
                est_idxs[-1], est_labels[-1] = \
                        self._postprocess(est_idxs[-1], est_labels[-1])
        except:
            # The audio file is too short, only beginning and end
            logging.warning("Audio file too short! "
                            "Only start and end boundaries.")
            est_idxs = [np.array([0, F.shape[1] - 1])]
            est_labels = [np.ones(1) * -1]

        return est_idxs, est_labels
<|endoftext|>


<commit_before>import sys
import time
import multiprocessing
from distutils.version import LooseVersion, StrictVersion

import marathon
from behave import given, when, then

from itest_utils import get_marathon_connection_string
sys.path.append('../')

@given('a working marathon instance')
def working_marathon(context):
    """Adds a working marathon client as context.client for the purposes of
    interacting with it in the test."""
    if not hasattr(context, 'client'):
        marathon_connection_string = "http://%s" % \
            get_marathon_connection_string()
        context.client = marathon.MarathonClient(marathon_connection_string)

@then(u'we get the marathon instance\'s info')
def get_marathon_info(context):
    assert context.client.get_info()

@when(u'we create a trivial new app')
def create_trivial_new_app(context):
    context.client.create_app('test-trivial-app', marathon.MarathonApp(cmd='sleep 3600', mem=16, cpus=0.1, instances=5))

@then(u'we should be able to kill the tasks')
def kill_a_task(context):
    time.sleep(5)
    app = context.client.get_app('test-trivial-app')
    tasks = app.tasks
    context.client.kill_task(app_id='test-trivial-app', task_id=tasks[0].id, scale=True)

@when(u'we create a complex new app')
def create_complex_new_app_with_unicode(context):
    app_config = {
        'container': {
            'type': 'DOCKER',
            'docker': {
                'portMappings': [{'protocol': 'tcp', 'containerPort': 8888, 'hostPort': 0}],
                'image': u'localhost/fake_docker_url',
                'network': 'BRIDGE',
                'parameters': [
                    {'key': 'add-host', 'value': 'google-public-dns-a.google.com:8.8.8.8'},
                ],
            },
            'volumes': [{'hostPath': u'/etc/stuff', 'containerPath': u'/etc/stuff', 'mode': 'RO'}],
        },
        'instances': 1,
        'mem': 30,
        'args': [],
        'backoff_factor': 2,
        'cpus': 0.25,
        'uris': ['file:///root/.dockercfg'],
        'backoff_seconds': 1,
        'constraints': None,
        'cmd': u'/bin/true',
        'health_checks': [
            {
                'protocol': 'HTTP',
                'path': '/health',
                'gracePeriodSeconds': 3,
                'intervalSeconds': 10,
                'portIndex': 0,
                'timeoutSeconds': 10,
                'maxConsecutiveFailures': 3
            },
        ],
    }
    context.client.create_app('test-complex-app', marathon.MarathonApp(**app_config))

@then(u'we should see the {which} app running via the marathon api')
def see_complext_app_running(context, which):
    print(context.client.list_apps())
    assert context.client.get_app('test-%s-app' % which)

@when(u'we wait the {which} app deployment finish')
def wait_deployment_finish(context, which):
    while True:
        time.sleep(1)
        app = context.client.get_app('test-%s-app' % which, embed_tasks=True)
        if not app.deployments:
            break

@then(u'we should be able to kill the #{to_kill} tasks of the {which} app')
def kill_tasks(context, to_kill, which):
    app_tasks = context.client.get_app('test-%s-app' % which, embed_tasks=True).tasks

    index_to_kill = eval("[" + to_kill + "]")
    task_to_kill = [app_tasks[index].id for index in index_to_kill]

    context.client.kill_given_tasks(task_to_kill)

@then(u'we should be able to list tasks of the {which} app')
def list_tasks(context, which):
    app = context.client.get_app('test-%s-app' % which)
    tasks = context.client.list_tasks('test-%s-app' % which)
    assert len(tasks) == app.instances

def listen_for_events(client, events):
    for msg in client.event_stream():
        events.append(msg)

@when(u'marathon version is greater than {version}')
def marathon_version_chech(context, version):
    info = context.client.get_info()
    if StrictVersion(info.version) < StrictVersion(version):
        context.scenario.skip(reason='Marathon version is too low for this scenario')

@when(u'we start listening for events')
def start_listening_stream(context):
    manager = multiprocessing.Manager()
    mlist = manager.list()
    context.manager = manager
    context.events = mlist
    p = multiprocessing.Process(target=listen_for_events, args=(context.client, mlist))
    p.start()
    context.p = p

@then(u'we should see list of events')
def stop_listening_stream(context):
    time.sleep(10)
    context.p.terminate()

    # event list should contain 5 status_update_event with taskStatus == TASK_RUNNING
    filtered_events = [e for e in context.events if e.event_type == "status_update_event" and e.task_status == "TASK_RUNNING"]
    assert len(filtered_events) == 5

    # and 1 status_update_event with taskStatus == TASK_KILLED
    filtered_events = [e for e in context.events if e.event_type == "status_update_event" and e.task_status == "TASK_KILLED"]
    assert len(filtered_events) == 1

    # and 1 deployment_step_success events with target instances == 5
    filtered_events = [e for e in context.events if e.event_type == "deployment_step_success" and e.plan.target.apps[0]['instances'] == 5]
    assert len(filtered_events) == 1

    # and 1 deployment_step_success events with target instances == 4
    filtered_events = [e for e in context.events if e.event_type == "deployment_step_success" and e.plan.target.apps[0]['instances'] == 4]
    assert len(filtered_events) == 1
<commit_msg>Reorganize test slightly<commit_after>import sys
import time
import multiprocessing
from distutils.version import LooseVersion, StrictVersion

import marathon
from behave import given, when, then

from itest_utils import get_marathon_connection_string
sys.path.append('../')

@given('a working marathon instance')
def working_marathon(context):
    """Adds a working marathon client as context.client for the purposes of
    interacting with it in the test."""
    if not hasattr(context, 'client'):
        marathon_connection_string = "http://%s" % \
            get_marathon_connection_string()
        context.client = marathon.MarathonClient(marathon_connection_string)

@then(u'we get the marathon instance\'s info')
def get_marathon_info(context):
    assert context.client.get_info()

@when(u'we create a trivial new app')
def create_trivial_new_app(context):
    context.client.create_app('test-trivial-app', marathon.MarathonApp(cmd='sleep 3600', mem=16, cpus=0.1, instances=5))

@then(u'we should be able to kill the tasks')
def kill_a_task(context):
    time.sleep(5)
    app = context.client.get_app('test-trivial-app')
    tasks = app.tasks
    context.client.kill_task(app_id='test-trivial-app', task_id=tasks[0].id, scale=True)

@when(u'we create a complex new app')
def create_complex_new_app_with_unicode(context):
    app_config = {
        'container': {
            'type': 'DOCKER',
            'docker': {
                'portMappings': [{'protocol': 'tcp', 'containerPort': 8888, 'hostPort': 0}],
                'image': u'localhost/fake_docker_url',
                'network': 'BRIDGE',
                'parameters': [
                    {'key': 'add-host', 'value': 'google-public-dns-a.google.com:8.8.8.8'},
                ],
            },
            'volumes': [{'hostPath': u'/etc/stuff', 'containerPath': u'/etc/stuff', 'mode': 'RO'}],
        },
        'instances': 1,
        'mem': 30,
        'args': [],
        'backoff_factor': 2,
        'cpus': 0.25,
        'uris': ['file:///root/.dockercfg'],
        'backoff_seconds': 1,
        'constraints': None,
        'cmd': u'/bin/true',
        'health_checks': [
            {
                'protocol': 'HTTP',
                'path': '/health',
                'gracePeriodSeconds': 3,
                'intervalSeconds': 10,
                'portIndex': 0,
                'timeoutSeconds': 10,
                'maxConsecutiveFailures': 3
            },
        ],
    }
    context.client.create_app('test-complex-app', marathon.MarathonApp(**app_config))

@then(u'we should see the {which} app running via the marathon api')
def see_complext_app_running(context, which):
    print(context.client.list_apps())
    assert context.client.get_app('test-%s-app' % which)

@when(u'we wait the {which} app deployment finish')
def wait_deployment_finish(context, which):
    while True:
        time.sleep(1)
        app = context.client.get_app('test-%s-app' % which, embed_tasks=True)
        if not app.deployments:
            break

@then(u'we should be able to kill the #{to_kill} tasks of the {which} app')
def kill_tasks(context, to_kill, which):
    app_tasks = context.client.get_app('test-%s-app' % which, embed_tasks=True).tasks

    index_to_kill = eval("[" + to_kill + "]")
    task_to_kill = [app_tasks[index].id for index in index_to_kill]

    context.client.kill_given_tasks(task_to_kill)

@then(u'we should be able to list tasks of the {which} app')
def list_tasks(context, which):
    app = context.client.get_app('test-%s-app' % which)
    tasks = context.client.list_tasks('test-%s-app' % which)
    assert len(tasks) == app.instances

def listen_for_events(client, events):
    for msg in client.event_stream():
        events.append(msg)

@when(u'marathon version is greater than {version}')
def marathon_version_chech(context, version):
    info = context.client.get_info()
    if StrictVersion(info.version) < StrictVersion(version):
        context.scenario.skip(reason='Marathon version is too low for this scenario')

@when(u'we start listening for events')
def start_listening_stream(context):
    manager = multiprocessing.Manager()
    mlist = manager.list()
    context.manager = manager
    context.events = mlist
    p = multiprocessing.Process(target=listen_for_events, args=(context.client, mlist))
    p.start()
    context.p = p

@then(u'we should see list of events')
def stop_listening_stream(context):
    time.sleep(10)
    context.p.terminate()

    # event list should contain 5 status_update_event with taskStatus == TASK_RUNNING
    filtered_events = [e for e in context.events if e.event_type == "status_update_event" and e.task_status == "TASK_RUNNING"]
    assert len(filtered_events) == 5

    # and 1 deployment_step_success events with target instances == 5
    filtered_events = [e for e in context.events if e.event_type == "deployment_step_success" and e.plan.target.apps[0]['instances'] == 5]
    assert len(filtered_events) == 1

    # and 1 status_update_event with taskStatus == TASK_KILLED
    filtered_events = [e for e in context.events if e.event_type == "status_update_event" and e.task_status == "TASK_KILLED"]
    assert len(filtered_events) == 1

    # and 1 deployment_step_success events with target instances == 4
    filtered_events = [e for e in context.events if e.event_type == "deployment_step_success" and e.plan.target.apps[0]['instances'] == 4]
    assert len(filtered_events) == 1
<|endoftext|>


<commit_before>"""

Python Interchangeable Virtual Instrument Library
Driver for Test Equity Model 140

Copyright (c) 2014 Jeff Wurzbach

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

"""

from .. import ivi
from .. import ics

class testequityf4(ivi.IviContainer)::
    "Watlow F4 controller used in TestEquity Enviromental Chambers"

    def __init__(self, *args, **kwargs):
        self.__dict__.setdefault('_instrument_id', '')

        super(testequityf4, self).__init__(*args, **kwargs)

        self._add_property('chamber_temperature', self._get_temperature)

        self._temperature_decimal_config = 1
        self._humidity_decimal_config = 1
        self._part_temperature_decimal_config = 1

    def _get_temperature_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._temperature_decimal_config = self._read_register(606)
           self._set_cache_valid()
       return self._temperature_decimal_config

    def _get_humidity_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._humidity_decimal_config = self._read_register(616)
           self._set_cache_valid()
       return self._humidity_decimal_config

    def _get_part_temperature_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._part_temperature_decimal_config = self._read_register(626)
           self._set_cache_valid()
       return self._part_temperature_decimal_config

    def _set_temperature_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(606,value)
       self._temperature_decimal_config= value

    def _set_humidity_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(616,value)
       self._humidity_decimal_config= value

    def _set_part_temperature_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(626,value)
       self._part_temperature_decimal_config= value

    def _get_temperature():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(100))
            if self._temperature_decimal_config==1:
                temperature=float(resp)/10
            else:
                temperature=float(resp)
            return temperature
        return 0

    def _get_humidity():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(104))
            if self._humidity_decimal_config==1:
                humidity=float(resp)/10
            else:
                humidity=float(resp)
            return humidity
        return 0

    def _get_part_temperature():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(108))
            if self._part_temperature_decimal_config==1:
                part_temperature=float(resp)/10
            else:
                part_temperature=float(resp)
            return part_temperature
        return 0

<commit_msg>added function to read back the temperature UOM settings<commit_after>"""

Python Interchangeable Virtual Instrument Library
Driver for Test Equity Model 140

Copyright (c) 2014 Jeff Wurzbach

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

"""

from .. import ivi
from .. import ics

class testequityf4(ivi.IviContainer)::
    "Watlow F4 controller used in TestEquity Enviromental Chambers"

    def __init__(self, *args, **kwargs):
        self.__dict__.setdefault('_instrument_id', '')

        super(testequityf4, self).__init__(*args, **kwargs)

        self._add_property('chamber_temperature', self._get_temperature)
        self._add_property('chamber_temperature_setpoint', self._get_temperature)

        self._temperature_decimal_config = 1 #default to 500 means 50.0degC
        self._humidity_decimal_config = 1 #default to 500 means 50.0%RH
        self._part_temperature_decimal_config = 1 #default to 500 means 50.0degC
        self._temperature_unit = 1 #default to degC

    #grab the decimal configrutions for the controller and chache them.  provide a method to change them if allowed (i.e. if someone changes the defualt config from TestEquity).
    def _get_temperature_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._temperature_decimal_config = self._read_register(606)
           self._set_cache_valid()
       return self._temperature_decimal_config

    def _get_humidity_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._humidity_decimal_config = self._read_register(616)
           self._set_cache_valid()
       return self._humidity_decimal_config

    def _get_part_temperature_decimal_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._part_temperature_decimal_config = self._read_register(626)
           self._set_cache_valid()
       return self._part_temperature_decimal_config

    def _set_temperature_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(606,value)
       self._temperature_decimal_config= value

    def _set_humidity_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(616,value)
       self._humidity_decimal_config= value

    def _set_part_temperature_decimal_config(self, value):
       value = int(value)
       if not self._driver_operation_simulate:
           self._write_register(626,value)
       self._part_temperature_decimal_config= value

    #Provide ability to read and write the config on the UOM for temperature.  Cache results to make sure things work nicely
     def _get_temperature_unit_config(self):
       if not self._driver_operation_simulate and not self._get_cache_valid():
           self._temperature_unit = self._read_register(901)
           self._set_cache_valid()
       return self._temperature_unit

     def _set_temperature_decimal_config(self, unit_of_measure="c"):
        self.driver_operation.invalidate_all_attributes()
        if unit_of_measure=="f":
            value = 0

            else:
                value = 1
        value = int(value)
        if not self._driver_operation_simulate:
            self._write_register(901,value)
        self._temperature_unit= value

    #_get_temperature(), _get_humidity(), and _get_part_temperature() are not cached so that the reads are accruate.
    def _get_temperature():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(100))
            if self._temperature_decimal_config==1:
                temperature=float(resp)/10
            else:
                temperature=float(resp)
            return temperature
        return 0

    def _get_humidity():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(104))
            if self._humidity_decimal_config==1:
                humidity=float(resp)/10
            else:
                humidity=float(resp)
            return humidity
        return 0

    def _get_part_temperature():
        if not self._driver_operation_simulate:
            resp=int(self._read_register(108))
            if self._part_temperature_decimal_config==1:
                part_temperature=float(resp)/10
            else:
                part_temperature=float(resp)
            return part_temperature
        return 0

<|endoftext|>


<commit_before>from ConfigParser import SafeConfigParser

from exit import warn

class ConfigParser(SafeConfigParser, object):

    def get(self, section, option, default=None):
        if self.has_option(section, option):
            return super(ConfigParser, self).get(section, option)
        return default

    def getboolean(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getboolean(section, option)
            except ValueError, e:
                warn('Not a boolean: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def getint(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getint(section, option)
            except ValueError, e:
                warn('Not an integer: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def getfloat(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getfloat(section, option)
            except ValueError, e:
                warn('Not a float: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def _value_from_exc(self, exc):
        # e.g.: invalid literal for int() with base 10: 'foo'
        msg = str(exc)
        colon = msg.find(':')
        if colon >= 0:
            value = msg[colon+1:].strip()
            if (value.startswith("'") and value.endswith("'")) or \
               (value.startswith('"') and value.endswith('"')):
                value = value[1:-1]
            return value
        return ''

<commit_msg>Rid empty line.<commit_after>from ConfigParser import SafeConfigParser
from exit import warn

class ConfigParser(SafeConfigParser, object):

    def get(self, section, option, default=None):
        if self.has_option(section, option):
            return super(ConfigParser, self).get(section, option)
        return default

    def getboolean(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getboolean(section, option)
            except ValueError, e:
                warn('Not a boolean: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def getint(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getint(section, option)
            except ValueError, e:
                warn('Not an integer: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def getfloat(self, section, option, default=None):
        if self.has_option(section, option):
            try:
                return super(ConfigParser, self).getfloat(section, option)
            except ValueError, e:
                warn('Not a float: %s = %s' % (option, self._value_from_exc(e)))
        return default

    def _value_from_exc(self, exc):
        # e.g.: invalid literal for int() with base 10: 'foo'
        msg = str(exc)
        colon = msg.find(':')
        if colon >= 0:
            value = msg[colon+1:].strip()
            if (value.startswith("'") and value.endswith("'")) or \
               (value.startswith('"') and value.endswith('"')):
                value = value[1:-1]
            return value
        return ''

<|endoftext|>


<commit_before># Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from threading import Lock

from mycroft import dialog
from mycroft.enclosure.api import EnclosureAPI
from mycroft.client.speech.listener import RecognizerLoop
from mycroft.configuration import Configuration
from mycroft.identity import IdentityManager
from mycroft.lock import Lock as PIDLock  # Create/Support PID locking file
from mycroft.messagebus.client import MessageBusClient
from mycroft.messagebus.message import Message
from mycroft.util import create_daemon, wait_for_exit_signal, \
    reset_sigint_handler, create_echo_function
from mycroft.util.log import LOG

bus = None  # Mycroft messagebus connection
lock = Lock()
loop = None
config = None

def handle_record_begin():
    """Forward internal bus message to external bus."""
    LOG.info("Begin Recording...")
    bus.emit(Message('recognizer_loop:record_begin'))

def handle_record_end():
    """Forward internal bus message to external bus."""
    LOG.info("End Recording...")
    bus.emit(Message('recognizer_loop:record_end'))

def handle_no_internet():
    LOG.debug("Notifying enclosure of no internet connection")
    bus.emit(Message('enclosure.notify.no_internet'))

def handle_awoken():
    """Forward mycroft.awoken to the messagebus."""
    LOG.info("Listener is now Awake: ")
    bus.emit(Message('mycroft.awoken'))

def handle_wakeword(event):
    LOG.info("Wakeword Detected: " + event['utterance'])
    bus.emit(Message('recognizer_loop:wakeword', event))

def handle_utterance(event):
    LOG.info("Utterance: " + str(event['utterances']))
    context = {'client_name': 'mycroft_listener',
               'source': 'audio',
               'destination': ["skills"]}
    if 'ident' in event:
        ident = event.pop('ident')
        context['ident'] = ident
    bus.emit(Message('recognizer_loop:utterance', event, context))

def handle_unknown():
    bus.emit(Message('mycroft.speech.recognition.unknown'))

def handle_speak(event):
    """
        Forward speak message to message bus.
    """
    bus.emit(Message('speak', event))

def handle_complete_intent_failure(event):
    """Extreme backup for answering completely unhandled intent requests."""
    LOG.info("Failed to find intent.")
    data = {'utterance': dialog.get('not.loaded')}
    bus.emit(Message('speak', data))

def handle_sleep(event):
    """Put the recognizer loop to sleep."""
    loop.sleep()

def handle_wake_up(event):
    """Wake up the the recognize loop."""
    loop.awaken()

def handle_mic_mute(event):
    """Mute the listener system."""
    loop.mute()

def handle_mic_unmute(event):
    """Unmute the listener system."""
    loop.unmute()

def handle_mic_listen(_):
    """Handler for mycroft.mic.listen.

    Starts listening as if wakeword was spoken.
    """
    loop.responsive_recognizer.trigger_listen()

def handle_mic_get_status(event):
    """Query microphone mute status."""
    data = {'muted': loop.is_muted()}
    bus.emit(event.response(data))

def handle_paired(event):
    """Update identity information with pairing data.

    This is done here to make sure it's only done in a single place.
    TODO: Is there a reason this isn't done directly in the pairing skill?
    """
    IdentityManager.update(event.data)

def handle_audio_start(event):
    """Mute recognizer loop."""
    if config.get("listener").get("mute_during_output"):
        loop.mute()

def handle_audio_end(event):
    """Request unmute, if more sources have requested the mic to be muted
    it will remain muted.
    """
    if config.get("listener").get("mute_during_output"):
        loop.unmute()  # restore

def handle_stop(event):
    """Handler for mycroft.stop, i.e. button press."""
    loop.force_unmute()

def handle_open():
    # TODO: Move this into the Enclosure (not speech client)
    # Reset the UI to indicate ready for speech processing
    EnclosureAPI(bus).reset()

def main():
    global bus
    global loop
    global config
    reset_sigint_handler()
    PIDLock("voice")
    bus = MessageBusClient()  # Mycroft messagebus, see mycroft.messagebus
    Configuration.set_config_update_handlers(bus)
    config = Configuration.get()

    # Register handlers on internal RecognizerLoop bus
    loop = RecognizerLoop()
    loop.on('recognizer_loop:utterance', handle_utterance)
    loop.on('recognizer_loop:speech.recognition.unknown', handle_unknown)
    loop.on('speak', handle_speak)
    loop.on('recognizer_loop:record_begin', handle_record_begin)
    loop.on('recognizer_loop:awoken', handle_awoken)
    loop.on('recognizer_loop:wakeword', handle_wakeword)
    loop.on('recognizer_loop:record_end', handle_record_end)
    loop.on('recognizer_loop:no_internet', handle_no_internet)

    # Register handlers for events on main Mycroft messagebus
    bus.on('open', handle_open)
    bus.on('complete_intent_failure', handle_complete_intent_failure)
    bus.on('recognizer_loop:sleep', handle_sleep)
    bus.on('recognizer_loop:wake_up', handle_wake_up)
    bus.on('mycroft.mic.mute', handle_mic_mute)
    bus.on('mycroft.mic.unmute', handle_mic_unmute)
    bus.on('mycroft.mic.get_status', handle_mic_get_status)
    bus.on('mycroft.mic.listen', handle_mic_listen)
    bus.on("mycroft.paired", handle_paired)
    bus.on('recognizer_loop:audio_output_start', handle_audio_start)
    bus.on('recognizer_loop:audio_output_end', handle_audio_end)
    bus.on('mycroft.stop', handle_stop)
    bus.on('message', create_echo_function('VOICE'))

    create_daemon(bus.run_forever)
    create_daemon(loop.run)

    wait_for_exit_signal()

if __name__ == "__main__":
    main()
<commit_msg>add context to all listener messages<commit_after># Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from threading import Lock

from mycroft import dialog
from mycroft.enclosure.api import EnclosureAPI
from mycroft.client.speech.listener import RecognizerLoop
from mycroft.configuration import Configuration
from mycroft.identity import IdentityManager
from mycroft.lock import Lock as PIDLock  # Create/Support PID locking file
from mycroft.messagebus.client import MessageBusClient
from mycroft.messagebus.message import Message
from mycroft.util import create_daemon, wait_for_exit_signal, \
    reset_sigint_handler, create_echo_function
from mycroft.util.log import LOG

bus = None  # Mycroft messagebus connection
lock = Lock()
loop = None
config = None

def handle_record_begin():
    """Forward internal bus message to external bus."""
    LOG.info("Begin Recording...")
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('recognizer_loop:record_begin', context=context))

def handle_record_end():
    """Forward internal bus message to external bus."""
    LOG.info("End Recording...")
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('recognizer_loop:record_end', context=context))

def handle_no_internet():
    LOG.debug("Notifying enclosure of no internet connection")
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('enclosure.notify.no_internet', context=context))

def handle_awoken():
    """Forward mycroft.awoken to the messagebus."""
    LOG.info("Listener is now Awake: ")
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('mycroft.awoken', context=context))

def handle_wakeword(event):
    LOG.info("Wakeword Detected: " + event['utterance'])
    bus.emit(Message('recognizer_loop:wakeword', event))

def handle_utterance(event):
    LOG.info("Utterance: " + str(event['utterances']))
    context = {'client_name': 'mycroft_listener',
               'source': 'audio',
               'destination': ["skills"]}
    if 'ident' in event:
        ident = event.pop('ident')
        context['ident'] = ident
    bus.emit(Message('recognizer_loop:utterance', event, context))

def handle_unknown():
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('mycroft.speech.recognition.unknown', context=context))

def handle_speak(event):
    """
        Forward speak message to message bus.
    """
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('speak', event, context))

def handle_complete_intent_failure(event):
    """Extreme backup for answering completely unhandled intent requests."""
    LOG.info("Failed to find intent.")
    data = {'utterance': dialog.get('not.loaded')}
    context = {'client_name': 'mycroft_listener',
               'source': 'audio'}
    bus.emit(Message('speak', data, context))

def handle_sleep(event):
    """Put the recognizer loop to sleep."""
    loop.sleep()

def handle_wake_up(event):
    """Wake up the the recognize loop."""
    loop.awaken()

def handle_mic_mute(event):
    """Mute the listener system."""
    loop.mute()

def handle_mic_unmute(event):
    """Unmute the listener system."""
    loop.unmute()

def handle_mic_listen(_):
    """Handler for mycroft.mic.listen.

    Starts listening as if wakeword was spoken.
    """
    loop.responsive_recognizer.trigger_listen()

def handle_mic_get_status(event):
    """Query microphone mute status."""
    data = {'muted': loop.is_muted()}
    bus.emit(event.response(data))

def handle_paired(event):
    """Update identity information with pairing data.

    This is done here to make sure it's only done in a single place.
    TODO: Is there a reason this isn't done directly in the pairing skill?
    """
    IdentityManager.update(event.data)

def handle_audio_start(event):
    """Mute recognizer loop."""
    if config.get("listener").get("mute_during_output"):
        loop.mute()

def handle_audio_end(event):
    """Request unmute, if more sources have requested the mic to be muted
    it will remain muted.
    """
    if config.get("listener").get("mute_during_output"):
        loop.unmute()  # restore

def handle_stop(event):
    """Handler for mycroft.stop, i.e. button press."""
    loop.force_unmute()

def handle_open():
    # TODO: Move this into the Enclosure (not speech client)
    # Reset the UI to indicate ready for speech processing
    EnclosureAPI(bus).reset()

def main():
    global bus
    global loop
    global config
    reset_sigint_handler()
    PIDLock("voice")
    bus = MessageBusClient()  # Mycroft messagebus, see mycroft.messagebus
    Configuration.set_config_update_handlers(bus)
    config = Configuration.get()

    # Register handlers on internal RecognizerLoop bus
    loop = RecognizerLoop()
    loop.on('recognizer_loop:utterance', handle_utterance)
    loop.on('recognizer_loop:speech.recognition.unknown', handle_unknown)
    loop.on('speak', handle_speak)
    loop.on('recognizer_loop:record_begin', handle_record_begin)
    loop.on('recognizer_loop:awoken', handle_awoken)
    loop.on('recognizer_loop:wakeword', handle_wakeword)
    loop.on('recognizer_loop:record_end', handle_record_end)
    loop.on('recognizer_loop:no_internet', handle_no_internet)

    # Register handlers for events on main Mycroft messagebus
    bus.on('open', handle_open)
    bus.on('complete_intent_failure', handle_complete_intent_failure)
    bus.on('recognizer_loop:sleep', handle_sleep)
    bus.on('recognizer_loop:wake_up', handle_wake_up)
    bus.on('mycroft.mic.mute', handle_mic_mute)
    bus.on('mycroft.mic.unmute', handle_mic_unmute)
    bus.on('mycroft.mic.get_status', handle_mic_get_status)
    bus.on('mycroft.mic.listen', handle_mic_listen)
    bus.on("mycroft.paired", handle_paired)
    bus.on('recognizer_loop:audio_output_start', handle_audio_start)
    bus.on('recognizer_loop:audio_output_end', handle_audio_end)
    bus.on('mycroft.stop', handle_stop)
    bus.on('message', create_echo_function('VOICE'))

    create_daemon(bus.run_forever)
    create_daemon(loop.run)

    wait_for_exit_signal()

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before># Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Event scheduler system for calling skill (and other) methods at a specific
times.
"""
import json
import time
from datetime import datetime, timedelta
from threading import Thread, Lock
from os.path import isfile, join, expanduser

from mycroft.configuration import Configuration
from mycroft.messagebus.message import Message
from mycroft.util.log import LOG

def repeat_time(sched_time, repeat):
    """Next scheduled time for repeating event. Guarantees that the
    time is not in the past (but could skip interim events)

    Args:
        sched_time (float): Scheduled unix time for the event
        repeat (float):     Repeat period in seconds

    Returns: (float) time for next event
    """
    next_time = sched_time + repeat
    while next_time < time.time():
        # Schedule at an offset to assure no doubles
        next_time = time.time() + abs(repeat)
    return next_time

class EventScheduler(Thread):
    """Create an event scheduler thread. Will send messages at a
     predetermined time to the registered targets.

    Arguments:
        bus:            Mycroft messagebus (mycroft.messagebus)
        schedule_file:  File to store pending events to on shutdown
    """
    def __init__(self, bus, schedule_file='schedule.json'):
        super().__init__()
        data_dir = expanduser(Configuration.get()['data_dir'])

        self.events = {}
        self.event_lock = Lock()

        self.bus = bus
        self.is_running = True
        self.schedule_file = join(data_dir, schedule_file)
        if self.schedule_file:
            self.load()

        self.bus.on('mycroft.scheduler.schedule_event',
                    self.schedule_event_handler)
        self.bus.on('mycroft.scheduler.remove_event',
                    self.remove_event_handler)
        self.bus.on('mycroft.scheduler.update_event',
                    self.update_event_handler)
        self.bus.on('mycroft.scheduler.get_event',
                    self.get_event_handler)
        self.start()

    def load(self):
        """Load json data with active events from json file."""
        if isfile(self.schedule_file):
            json_data = {}
            with open(self.schedule_file) as f:
                try:
                    json_data = json.load(f)
                except Exception as e:
                    LOG.error(e)
            current_time = time.time()
            with self.event_lock:
                for key in json_data:
                    event_list = json_data[key]
                    # discard non repeating events that has already happened
                    self.events[key] = [tuple(e) for e in event_list
                                        if e[0] > current_time or e[1]]

    def run(self):
        while self.is_running:
            self.check_state()
            time.sleep(0.5)

    def check_state(self):
        """Check if an event should be triggered."""
        with self.event_lock:
            # Check all events
            pending_messages = []
            for event in self.events:
                current_time = time.time()
                e = self.events[event]
                # Get scheduled times that has passed
                passed = [(t, r, d) for (t, r, d) in e if t <= current_time]
                # and remaining times that we're still waiting for
                remaining = [(t, r, d) for t, r, d in e if t > current_time]
                # Trigger registered methods
                for sched_time, repeat, data in passed:
                    pending_messages.append(Message(event, data))
                    # if this is a repeated event add a new trigger time
                    if repeat:
                        next_time = repeat_time(sched_time, repeat)
                        remaining.append((next_time, repeat, data))
                # update list of events
                self.events[event] = remaining

        # Remove events have are now completed
        self.clear_empty()

        # Finally, emit the queued up events that triggered
        for msg in pending_messages:
            self.bus.emit(msg)

    def schedule_event(self, event, sched_time, repeat=None, data=None):
        """Add event to pending event schedule.

        Arguments:
            event (str): Handler for the event
            sched_time ([type]): [description]
            repeat ([type], optional): Defaults to None. [description]
            data ([type], optional): Defaults to None. [description]
        """
        data = data or {}
        with self.event_lock:
            # get current list of scheduled times for event, [] if missing
            event_list = self.events.get(event, [])

            # Don't schedule if the event is repeating and already scheduled
            if repeat and event in self.events:
                LOG.debug('Repeating event {} is already scheduled, discarding'
                          .format(event))
            else:
                # add received event and time
                event_list.append((sched_time, repeat, data))
                self.events[event] = event_list

    def schedule_event_handler(self, message):
        """Messagebus interface to the schedule_event method.
        Required data in the message envelope is
            event: event to emit
            time:  time to emit the event

        Optional data is
            repeat: repeat interval
            data:   data to send along with the event
        """
        event = message.data.get('event')
        sched_time = message.data.get('time')
        repeat = message.data.get('repeat')
        data = message.data.get('data')
        if event and sched_time:
            self.schedule_event(event, sched_time, repeat, data)
        elif not event:
            LOG.error('Scheduled event name not provided')
        else:
            LOG.error('Scheduled event time not provided')

    def remove_event(self, event):
        """Remove an event from the list of scheduled events.

        Arguments:
            event (str): event identifier
        """
        with self.event_lock:
            if event in self.events:
                self.events.pop(event)

    def remove_event_handler(self, message):
        """Messagebus interface to the remove_event method."""
        event = message.data.get('event')
        self.remove_event(event)

    def update_event(self, event, data):
        """Change an existing events data.

        This will only update the first call if multiple calls are registered
        to the same event identifier.

        Arguments:
            event (str): event identifier
            data (dict): new data
        """
        with self.event_lock:
            # if there is an active event with this name
            if len(self.events.get(event, [])) > 0:
                time, repeat, _ = self.events[event][0]
                self.events[event][0] = (time, repeat, data)

    def update_event_handler(self, message):
        """Messagebus interface to the update_event method."""
        event = message.data.get('event')
        data = message.data.get('data')
        self.update_event(event, data)

    def get_event_handler(self, message):
        """Messagebus interface to get_event.

        Emits another event sending event status.
        """
        event_name = message.data.get("name")
        event = None
        with self.event_lock:
            if event_name in self.events:
                event = self.events[event_name]
        emitter_name = 'mycroft.event_status.callback.{}'.format(event_name)
        self.bus.emit(message.reply(emitter_name, data=event))

    def store(self):
        """Write current schedule to disk."""
        with self.event_lock:
            with open(self.schedule_file, 'w') as f:
                json.dump(self.events, f)

    def clear_repeating(self):
        """Remove repeating events from events dict."""
        with self.event_lock:
            for e in self.events:
                self.events[e] = [i for i in self.events[e] if i[1] is None]

    def clear_empty(self):
        """Remove empty event entries from events dict."""
        with self.event_lock:
            self.events = {k: self.events[k] for k in self.events
                           if self.events[k] != []}

    def shutdown(self):
        """Stop the running thread."""
        self.is_running = False
        # Remove listeners
        self.bus.remove_all_listeners('mycroft.scheduler.schedule_event')
        self.bus.remove_all_listeners('mycroft.scheduler.remove_event')
        self.bus.remove_all_listeners('mycroft.scheduler.update_event')
        # Wait for thread to finish
        self.join()
        # Prune event list in preparation for saving
        self.clear_repeating()
        self.clear_empty()
        # Store all pending scheduled events
        self.store()

class EventSchedulerInterface:
    """Skill interface for accessing the event scheduler."""
    def __init__(self, skill):
        self.skill = skill
        self.scheduled_repeats = []

    def _unique_name(self, name):
        """Return a name unique to this skill using the format
        [skill_id]:[name].

        Arguments:
            name:   Name to use internally

        Returns:
            str: name unique to this skill
        """
        return str(self.skill.skill_id) + ':' + (name or '')

    def _schedule_event(self, handler, when, data=None, name=None,
                        repeat=None):
        """Underlying method for schedule_event and schedule_repeating_event.

        Takes scheduling information and sends it off on the message bus.
        """
        if not name:
            name = self.skill.name + handler.__name__
        unique_name = self._unique_name(name)
        if repeat:
            self.scheduled_repeats.append(name)  # store "friendly name"

        data = data or {}
        self.skill.add_event(unique_name, handler, once=not repeat)
        event_data = {}
        event_data['time'] = time.mktime(when.timetuple())
        event_data['event'] = unique_name
        event_data['repeat'] = repeat
        event_data['data'] = data
        self.skill.bus.emit(Message('mycroft.scheduler.schedule_event',
                                    data=event_data))

    def schedule_event(self, handler, when, data=None, name=None):
        """Schedule a single-shot event.

        Arguments:
            handler:               method to be called
            when (datetime/int/float):   datetime (in system timezone) or
                                   number of seconds in the future when the
                                   handler should be called
            data (dict, optional): data to send when the handler is called
            name (str, optional):  reference name
                                   NOTE: This will not warn or replace a
                                   previously scheduled event of the same
                                   name.
        """
        data = data or {}
        if isinstance(when, (int, float)):
            when = datetime.now() + timedelta(seconds=when)
        self._schedule_event(handler, when, data, name)

    def schedule_repeating_event(self, handler, when, frequency,
                                 data=None, name=None):
        """Schedule a repeating event.

        Arguments:
            handler:                method to be called
            when (datetime):        time (in system timezone) for first
                                    calling the handler, or None to
                                    initially trigger <frequency> seconds
                                    from now
            frequency (float/int):  time in seconds between calls
            data (dict, optional):  data to send when the handler is called
            name (str, optional):   reference name, must be unique
        """
        # Do not schedule if this event is already scheduled by the skill
        if name not in self.scheduled_repeats:
            data = data or {}
            if not when:
                when = datetime.now() + timedelta(seconds=frequency)
            self._schedule_event(handler, when, data, name, frequency)
        else:
            LOG.debug('The event is already scheduled, cancel previous '
                      'event if this scheduling should replace the last.')

    def update_scheduled_event(self, name, data=None):
        """Change data of event.

        Arguments:
            name (str): reference name of event (from original scheduling)
        """
        data = data or {}
        data = {
            'event': self._unique_name(name),
            'data': data
        }
        self.skill.bus.emit(Message('mycroft.schedule.update_event',
                                    data=data))

    def cancel_scheduled_event(self, name):
        """Cancel a pending event. The event will no longer be scheduled
        to be executed

        Arguments:
            name (str): reference name of event (from original scheduling)
        """
        unique_name = self._unique_name(name)
        data = {'event': unique_name}
        if name in self.scheduled_repeats:
            self.scheduled_repeats.remove(name)
        if self.skill.remove_event(unique_name):
            self.skill.bus.emit(Message('mycroft.scheduler.remove_event',
                                        data=data))

    def get_scheduled_event_status(self, name):
        """Get scheduled event data and return the amount of time left

        Arguments:
            name (str): reference name of event (from original scheduling)

        Returns:
            int: the time left in seconds

        Raises:
            Exception: Raised if event is not found
        """
        event_name = self._unique_name(name)
        data = {'name': event_name}

        # making event_status an object so it's refrence can be changed
        event_status = None
        finished_callback = False

        def callback(message):
            nonlocal event_status
            nonlocal finished_callback
            if message.data is not None:
                event_time = int(message.data[0][0])
                current_time = int(time.time())
                time_left_in_seconds = event_time - current_time
                event_status = time_left_in_seconds
            finished_callback = True

        emitter_name = 'mycroft.event_status.callback.{}'.format(event_name)
        self.skill.bus.once(emitter_name, callback)
        self.skill.bus.emit(Message('mycroft.scheduler.get_event', data=data))

        start_wait = time.time()
        while finished_callback is False and time.time() - start_wait < 3.0:
            time.sleep(0.1)
        if time.time() - start_wait > 3.0:
            raise Exception("Event Status Messagebus Timeout")
        return event_status

    def cancel_all_repeating_events(self):
        """Cancel any repeating events started by the skill."""
        # NOTE: Gotta make a copy of the list due to the removes that happen
        #       in cancel_scheduled_event().
        for e in list(self.scheduled_repeats):
            self.cancel_scheduled_event(e)
<commit_msg>Update docstrings and variable names of EventSchedulerInterface<commit_after># Copyright 2017 Mycroft AI Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Event scheduler system for calling skill (and other) methods at a specific
times.
"""
import json
import time
from datetime import datetime, timedelta
from threading import Thread, Lock
from os.path import isfile, join, expanduser

from mycroft.configuration import Configuration
from mycroft.messagebus.message import Message
from mycroft.util.log import LOG

def repeat_time(sched_time, repeat):
    """Next scheduled time for repeating event. Guarantees that the
    time is not in the past (but could skip interim events)

    Args:
        sched_time (float): Scheduled unix time for the event
        repeat (float):     Repeat period in seconds

    Returns: (float) time for next event
    """
    next_time = sched_time + repeat
    while next_time < time.time():
        # Schedule at an offset to assure no doubles
        next_time = time.time() + abs(repeat)
    return next_time

class EventScheduler(Thread):
    """Create an event scheduler thread. Will send messages at a
     predetermined time to the registered targets.

    Arguments:
        bus:            Mycroft messagebus (mycroft.messagebus)
        schedule_file:  File to store pending events to on shutdown
    """
    def __init__(self, bus, schedule_file='schedule.json'):
        super().__init__()
        data_dir = expanduser(Configuration.get()['data_dir'])

        self.events = {}
        self.event_lock = Lock()

        self.bus = bus
        self.is_running = True
        self.schedule_file = join(data_dir, schedule_file)
        if self.schedule_file:
            self.load()

        self.bus.on('mycroft.scheduler.schedule_event',
                    self.schedule_event_handler)
        self.bus.on('mycroft.scheduler.remove_event',
                    self.remove_event_handler)
        self.bus.on('mycroft.scheduler.update_event',
                    self.update_event_handler)
        self.bus.on('mycroft.scheduler.get_event',
                    self.get_event_handler)
        self.start()

    def load(self):
        """Load json data with active events from json file."""
        if isfile(self.schedule_file):
            json_data = {}
            with open(self.schedule_file) as f:
                try:
                    json_data = json.load(f)
                except Exception as e:
                    LOG.error(e)
            current_time = time.time()
            with self.event_lock:
                for key in json_data:
                    event_list = json_data[key]
                    # discard non repeating events that has already happened
                    self.events[key] = [tuple(e) for e in event_list
                                        if e[0] > current_time or e[1]]

    def run(self):
        while self.is_running:
            self.check_state()
            time.sleep(0.5)

    def check_state(self):
        """Check if an event should be triggered."""
        with self.event_lock:
            # Check all events
            pending_messages = []
            for event in self.events:
                current_time = time.time()
                e = self.events[event]
                # Get scheduled times that has passed
                passed = [(t, r, d) for (t, r, d) in e if t <= current_time]
                # and remaining times that we're still waiting for
                remaining = [(t, r, d) for t, r, d in e if t > current_time]
                # Trigger registered methods
                for sched_time, repeat, data in passed:
                    pending_messages.append(Message(event, data))
                    # if this is a repeated event add a new trigger time
                    if repeat:
                        next_time = repeat_time(sched_time, repeat)
                        remaining.append((next_time, repeat, data))
                # update list of events
                self.events[event] = remaining

        # Remove events have are now completed
        self.clear_empty()

        # Finally, emit the queued up events that triggered
        for msg in pending_messages:
            self.bus.emit(msg)

    def schedule_event(self, event, sched_time, repeat=None, data=None):
        """Add event to pending event schedule.

        Arguments:
            event (str): Handler for the event
            sched_time ([type]): [description]
            repeat ([type], optional): Defaults to None. [description]
            data ([type], optional): Defaults to None. [description]
        """
        data = data or {}
        with self.event_lock:
            # get current list of scheduled times for event, [] if missing
            event_list = self.events.get(event, [])

            # Don't schedule if the event is repeating and already scheduled
            if repeat and event in self.events:
                LOG.debug('Repeating event {} is already scheduled, discarding'
                          .format(event))
            else:
                # add received event and time
                event_list.append((sched_time, repeat, data))
                self.events[event] = event_list

    def schedule_event_handler(self, message):
        """Messagebus interface to the schedule_event method.
        Required data in the message envelope is
            event: event to emit
            time:  time to emit the event

        Optional data is
            repeat: repeat interval
            data:   data to send along with the event
        """
        event = message.data.get('event')
        sched_time = message.data.get('time')
        repeat = message.data.get('repeat')
        data = message.data.get('data')
        if event and sched_time:
            self.schedule_event(event, sched_time, repeat, data)
        elif not event:
            LOG.error('Scheduled event name not provided')
        else:
            LOG.error('Scheduled event time not provided')

    def remove_event(self, event):
        """Remove an event from the list of scheduled events.

        Arguments:
            event (str): event identifier
        """
        with self.event_lock:
            if event in self.events:
                self.events.pop(event)

    def remove_event_handler(self, message):
        """Messagebus interface to the remove_event method."""
        event = message.data.get('event')
        self.remove_event(event)

    def update_event(self, event, data):
        """Change an existing events data.

        This will only update the first call if multiple calls are registered
        to the same event identifier.

        Arguments:
            event (str): event identifier
            data (dict): new data
        """
        with self.event_lock:
            # if there is an active event with this name
            if len(self.events.get(event, [])) > 0:
                time, repeat, _ = self.events[event][0]
                self.events[event][0] = (time, repeat, data)

    def update_event_handler(self, message):
        """Messagebus interface to the update_event method."""
        event = message.data.get('event')
        data = message.data.get('data')
        self.update_event(event, data)

    def get_event_handler(self, message):
        """Messagebus interface to get_event.

        Emits another event sending event status.
        """
        event_name = message.data.get("name")
        event = None
        with self.event_lock:
            if event_name in self.events:
                event = self.events[event_name]
        emitter_name = 'mycroft.event_status.callback.{}'.format(event_name)
        self.bus.emit(message.reply(emitter_name, data=event))

    def store(self):
        """Write current schedule to disk."""
        with self.event_lock:
            with open(self.schedule_file, 'w') as f:
                json.dump(self.events, f)

    def clear_repeating(self):
        """Remove repeating events from events dict."""
        with self.event_lock:
            for e in self.events:
                self.events[e] = [i for i in self.events[e] if i[1] is None]

    def clear_empty(self):
        """Remove empty event entries from events dict."""
        with self.event_lock:
            self.events = {k: self.events[k] for k in self.events
                           if self.events[k] != []}

    def shutdown(self):
        """Stop the running thread."""
        self.is_running = False
        # Remove listeners
        self.bus.remove_all_listeners('mycroft.scheduler.schedule_event')
        self.bus.remove_all_listeners('mycroft.scheduler.remove_event')
        self.bus.remove_all_listeners('mycroft.scheduler.update_event')
        # Wait for thread to finish
        self.join()
        # Prune event list in preparation for saving
        self.clear_repeating()
        self.clear_empty()
        # Store all pending scheduled events
        self.store()

class EventSchedulerInterface:
    """Interface for accessing the event scheduler over the message bus."""
    def __init__(self, skill):
        self.skill = skill
        self.scheduled_repeats = []

    def _create_unique_name(self, name):
        """Return a name unique to this skill using the format
        [skill_id]:[name].

        Arguments:
            name:   Name to use internally

        Returns:
            str: name unique to this skill
        """
        return str(self.skill.skill_id) + ':' + (name or '')

    def _schedule_event(self, handler, when, data, name, repeat_interval=None):
        """Underlying method for schedule_event and schedule_repeating_event.

        Takes scheduling information and sends it off on the message bus.

        Arguments:
            handler:                method to be called
            when (datetime):        time (in system timezone) for first
                                    calling the handler, or None to
                                    initially trigger <frequency> seconds
                                    from now
            data (dict, optional):  data to send when the handler is called
            name (str, optional):   reference name, must be unique
            repeat_interval (float/int):  time in seconds between calls

        """
        if isinstance(when, (int, float)) and when >= 0:
            when = datetime.now() + timedelta(seconds=when)
        if not name:
            name = self.skill.name + handler.__name__
        unique_name = self._create_unique_name(name)
        if repeat_interval:
            self.scheduled_repeats.append(name)  # store "friendly name"

        data = data or {}
        self.skill.add_event(unique_name, handler, once=not repeat_interval)
        event_data = {'time': time.mktime(when.timetuple()),
                      'event': unique_name,
                      'repeat': repeat_interval,
                      'data': data}
        self.skill.bus.emit(Message('mycroft.scheduler.schedule_event',
                                    data=event_data))

    def schedule_event(self, handler, when, data=None, name=None):
        """Schedule a single-shot event.

        Arguments:
            handler:               method to be called
            when (datetime/int/float):   datetime (in system timezone) or
                                   number of seconds in the future when the
                                   handler should be called
            data (dict, optional): data to send when the handler is called
            name (str, optional):  reference name
                                   NOTE: This will not warn or replace a
                                   previously scheduled event of the same
                                   name.
        """
        self._schedule_event(handler, when, data, name)

    def schedule_repeating_event(self, handler, when, interval,
                                 data=None, name=None):
        """Schedule a repeating event.

        Arguments:
            handler:                method to be called
            when (datetime):        time (in system timezone) for first
                                    calling the handler, or None to
                                    initially trigger <frequency> seconds
                                    from now
            interval (float/int):   time in seconds between calls
            data (dict, optional):  data to send when the handler is called
            name (str, optional):   reference name, must be unique
        """
        # Do not schedule if this event is already scheduled by the skill
        if name not in self.scheduled_repeats:
            # If only interval is given set to trigger in [interval] seconds
            # from now.
            if not when:
                when = datetime.now() + timedelta(seconds=interval)
            self._schedule_event(handler, when, data, name, interval)
        else:
            LOG.debug('The event is already scheduled, cancel previous '
                      'event if this scheduling should replace the last.')

    def update_scheduled_event(self, name, data=None):
        """Change data of event.

        Arguments:
            name (str): reference name of event (from original scheduling)
        """
        data = data or {}
        data = {
            'event': self._create_unique_name(name),
            'data': data
        }
        self.skill.bus.emit(Message('mycroft.schedule.update_event',
                                    data=data))

    def cancel_scheduled_event(self, name):
        """Cancel a pending event. The event will no longer be scheduled
        to be executed

        Arguments:
            name (str): reference name of event (from original scheduling)
        """
        unique_name = self._create_unique_name(name)
        data = {'event': unique_name}
        if name in self.scheduled_repeats:
            self.scheduled_repeats.remove(name)
        if self.skill.remove_event(unique_name):
            self.skill.bus.emit(Message('mycroft.scheduler.remove_event',
                                        data=data))

    def get_scheduled_event_status(self, name):
        """Get scheduled event data and return the amount of time left

        Arguments:
            name (str): reference name of event (from original scheduling)

        Returns:
            int: the time left in seconds

        Raises:
            Exception: Raised if event is not found
        """
        event_name = self._create_unique_name(name)
        data = {'name': event_name}

        # making event_status an object so it's refrence can be changed
        event_status = None
        finished_callback = False

        def callback(message):
            nonlocal event_status
            nonlocal finished_callback
            if message.data is not None:
                event_time = int(message.data[0][0])
                current_time = int(time.time())
                time_left_in_seconds = event_time - current_time
                event_status = time_left_in_seconds
            finished_callback = True

        emitter_name = 'mycroft.event_status.callback.{}'.format(event_name)
        self.skill.bus.once(emitter_name, callback)
        self.skill.bus.emit(Message('mycroft.scheduler.get_event', data=data))

        start_wait = time.time()
        while finished_callback is False and time.time() - start_wait < 3.0:
            time.sleep(0.1)
        if time.time() - start_wait > 3.0:
            raise Exception("Event Status Messagebus Timeout")
        return event_status

    def cancel_all_repeating_events(self):
        """Cancel any repeating events started by the skill."""
        # NOTE: Gotta make a copy of the list due to the removes that happen
        #       in cancel_scheduled_event().
        for e in list(self.scheduled_repeats):
            self.cancel_scheduled_event(e)
<|endoftext|>