<commit_before>from .base import MetafetishModuleBase
import redis
import cgi

class MowCounterTransactions(object):
    def __init__(self):
        pass

    def add_sticker_request(self, user_id, file_id):
        pass

    def remove_sticker_request(self, file_id):
        pass

    def add_sticker(self, user_id, file_id, value):
        pass

    def remove_sticker(self, file_id):
        pass

    def get_stickers(self):
        pass

    def update_mow_count(self, user_id, group_id, mow_count):
        pass

    def reset_counts(self):
        pass

    def get_group_list(self):
        pass

    def get_own_count(self, user_id):
        pass

    def get_group_top10(self, group_id):
        pass

    def get_global_top10(self, group_id):
        pass

class MowRedisTransactions(MowCounterTransactions):
    def __init__(self):
        # self.redis = redis.StrictRedis(host='localhost',
        #                                db=0,
        #                                decode_responses=True)

    def add_sticker_request(self, user_id, file_id):
        self.redis.hmset("sticker-requests", {file_id: user_id})

    def remove_sticker_request(self, file_id):
        self.redis.hdel("sticker-requests", file_id)

    def get_sticker_requests(self):
        return self.redis.hgetall("sticker-requests")

    def get_stickers(self):
        return self.redis.hgetall("sticker-values")

    def get_sticker_value(self, sticker_id):
        v = self.redis.hget("sticker-values", sticker_id)
        if v is not None:
            return int(v)
        return None

    def get_user(self, user_id):
        return self.redis.hgetall(user_id)

    def add_sticker(self, user_id, file_id, value):
        self.redis.hset("sticker-values", file_id, value)
        self.redis.hset("sticker-users", file_id, user_id)

    def remove_sticker(self, file_id):
        self.redis.hdel("sticker-values", file_id)
        self.redis.hdel("sticker-users", file_id)

    def get_sticker_values(self):
        return self.redis.hgetall("sticker-values")

    def update_mow_count(self, user_id, user_username, user_fname,
                         user_lname, group_id, group_title, mow_count):
        user_id = str(user_id)
        group_id = str(group_id)
        self.redis.hmset(user_id,
                         {"username": user_username,
                          "first-name": user_fname,
                          "last-name": user_lname})
        self.redis.sadd("groups", group_id)
        self.redis.hmset(group_id, {"title": group_title})
        self.redis.zincrby("user-scores", user_id, mow_count)
        self.redis.zincrby(group_id + "-scores", user_id, mow_count)

    def get_group_list(self):
        return self.redis.smembers("groups")

    def get_group(self, group_id):
        return self.redis.hgetall(group_id)

    def add_group(self, group_id):
        self.redis.sadd("groups", group_id)

    def reset_counts(self):
        self.redis.delete("user-scores")
        groups = self.redis.smembers("groups")
        for g in groups:
            self.redis.delete(g + "-scores")

    def get_own_count(self, user_id, group_id):
        user_id = str(user_id)
        group_id = str(group_id)
        r = {"local_score": 0,
             "local_rank": 0,
             "local_total": 0,
             "global_score": 0,
             "global_rank": 0,
             "global_total": 0}

        # global score
        r["global_score"] = self.redis.zscore("user-scores", user_id)
        if r["global_score"] is None:
            return None
        # global rank
        r["global_rank"] = self.redis.zrevrank("user-scores", user_id)
        # global total
        r["global_total"] = self.redis.zcard("user-scores")

        # local score
        r["local_score"] = self.redis.zscore(group_id + "-scores", user_id)
        # local rank
        r["local_rank"] = self.redis.zrevrank(group_id + "-scores", user_id)
        # local total
        r["local_total"] = self.redis.zcard(group_id + "-scores")
        if not r["local_score"]:
            r["local_score"] = 0
            if not r["local_total"]:
                r["local_total"] = 0
            r["local_rank"] = r["local_total"]
        return r

    def get_group_top10(self, group_id):
        group_id = str(group_id)
        top10 = self.redis.zrevrange(group_id + "-scores", 0, 10,
                                     withscores=True, score_cast_func=int)
        top10list = []
        for (user_id, score) in top10:
            user_dict = {}
            u = self.redis.hgetall(user_id)
            user_dict["name"] = u["first-name"] + u["last-name"]
            user_dict["score"] = score
            top10list.append(user_dict)
        return top10list

    def get_global_top10(self, group_id):
        group_id = str(group_id)
        top10 = self.redis.zrevrange("user-scores", 0, 10,
                                     withscores=True, score_cast_func=int)
        top10list = []
        for (user_id, score) in top10:
            user_dict = {}
            u = self.redis.hgetall(user_id)
            user_dict["name"] = u["first-name"] + u["last-name"]
            user_dict["score"] = score
            top10list.append(user_dict)
        return top10list

class MowMySQLTransactions(MowCounterTransactions):
    pass

class MowCounter(MetafetishModuleBase):
    def __init__(self, dbdir, cm):
        super().__init__(__name__)
        self.store = MowRedisTransactions()
        self.cm = cm

    def rm_sticker(self, bot, update):
        sticker = None
        while True:
            bot.sendMessage(update.message.chat.id,
                            text="Send me the sticker you'd like to add, or /cancel.")
            (bot, update) = yield
            if update.message.sticker is not None:
                sticker = update.message.sticker
                break
            bot.sendMessage(update.message.chat.id,
                            text="That's not a sticker!")
        if sticker.file_id not in self.store.get_stickers().keys():
            bot.sendMessage(update.message.chat.id,
                            text="I don't know that sticker!")
            return
        self.store.remove_sticker(sticker.file_id)
        bot.sendMessage(update.message.chat.id,
                        text="Sticker removed!")

    def check_mows(self, bot, update):
        user = update.message.from_user
        chat = update.message.chat
        mows = 0
        # The API tests for text as either text or '', not None. God damnit.
        if len(update.message.text) is not 0:
            # count mows. Maximum one mow per message
            mows = 1 if update.message.text.lower().count("mow") else 0
        elif update.message.sticker is not None:
            sticker = update.message.sticker
            # Make sure we have the sticker and it's accepted
            value = self.store.get_sticker_value(sticker.file_id)
            if value is None:
                return
            mows = value
        if mows is 0:
            return
        self.store.update_mow_count(user.id, user.username, user.first_name,
                                    user.last_name, chat.id, chat.title, mows)

    def dump(self):
        pass

    def show_own_count(self, bot, update):
        user_id = str(update.message.from_user.id)
        chat_id = str(update.message.chat.id)
        user = update.message.from_user
        r = self.store.get_own_count(user_id, chat_id)
        if r is None:
            bot.sendMessage(update.message.chat.id,
                            text="%s %s has no mows!" % (user.first_name, user.last_name))
            return
        bot.sendMessage(update.message.chat.id,
                        text="%s %s has mowed %d times in this group (Rank: %d of %d), and %d times globally (Rank: %d of %d)." %
                        (user.first_name, user.last_name,
                         r["local_score"], r["local_rank"] + 1, r["local_total"],
                         r["global_score"], r["global_rank"] + 1, r["global_total"]))

    def show_top10_count(self, bot, update):
        chat_id = str(update.message.chat.id)
        group_top10 = self.store.get_group_top10(chat_id)
        global_top10 = self.store.get_global_top10(chat_id)
        msg = "Top 10 Count for Group '%s':\n\n" % update.message.chat.title
        i = 1
        for u in group_top10:
            msg += "%d. " + cgi.escape("%s - %s\n" % (i, u["name"], u["score"]))
            i += 1
        msg += "\nTop 10 Count Globally:\n\n"
        i = 1
        for u in global_top10:
            msg += "%d. " + cgi.escape("%s - %s\n" % (i, u["name"], u["score"]))
            i += 1
        bot.sendMessage(update.message.chat.id,
                        text=msg,
                        parse_mode="HTML")

    def list_groups(self, bot, update):
        group_names = []
        for g in self.store.get_group_list():
            try:
                chat = bot.getChat(g)
            except:
                print("Cannot access chat %s?" % g)
                continue
            status = bot.getChatMember(g, bot.id)
            group_names.append("%s - @%s - %s - %s" % (chat.title, chat.username, g, status.status))
        bot.sendMessage(update.message.chat.id,
                        text="Groups I am currently counting mows in:\n %s" % "\n".join(group_names))
        pass

    def list_stickers(self, bot, update):
        bot.sendMessage(update.message.chat.id,
                        text="Here's the list of stickers that count for/against mow counts:")
        for (k, v) in self.store.get_stickers().items():
            bot.sendSticker(update.message.chat.id,
                            k)
            bot.sendMessage(update.message.chat.id,
                            text="^^^^ Sticker Value: %d" % int(v))

    def request_sticker(self, bot, update):
        sticker = None
        while True:
            bot.sendMessage(update.message.chat.id,
                            text="Send me the sticker you'd like to request to be added, or /cancel.")
            (bot, update) = yield
            if update.message.sticker is not None:
                sticker = update.message.sticker
                break
            bot.sendMessage(update.message.chat.id,
                            text="That's not a sticker!")
        if sticker.file_id in self.store.get_stickers().keys() or sticker.file_id in self.store.get_sticker_requests().keys():
            bot.sendMessage(update.message.chat.id,
                            text="I'm already tracking or waiting to review that sticker!")
            return
        self.store.add_sticker_request(update.message.from_user.id,
                                       sticker.file_id)
        bot.sendMessage(update.message.chat.id,
                        text="Sticker requested! The admins will review the sticker and add it if it is mow-worthy. Thanks!")

    def review_stickers(self, bot, update):
        for (sticker_id, user_id) in self.store.get_sticker_requests().items():
            while True:
                bot.sendSticker(update.message.chat.id,
                                sticker_id)
                bot.sendMessage(update.message.chat.id,
                                text="Send 0 to reject sticker, or a pos/neg int to specify sticker value, or /cancel.")
                (bot, update) = yield
                try:
                    value = int(update.message.text)
                    if value != 0:
                        self.store.add_sticker(user_id, sticker_id, value)
                        bot.sendMessage(update.message.chat.id,
                                        text="Sticker added with value %d" % value)
                    self.store.remove_sticker_request(sticker_id)
                    break
                except:
                    continue
        bot.sendMessage(update.message.chat.id,
                        text="Sticker review done!")

    def reset(self, bot, update):
        self.store.reset_counts()
        bot.sendMessage(update.message.chat.id,
                        text="Mow counts reset!")

    def broadcast_message(self, bot, update):
        bot.sendMessage(update.message.chat.id,
                        text="What message would you like to broadcast to groups I'm in?")
        (bot, update) = yield
        message = update.message.text
        groups = self.store.get_group_list()
        for g in groups:
            bot.sendMessage(g,
                            text=message)
<commit_msg>Shuffle arguments<commit_after>from .base import MetafetishModuleBase
import redis
import cgi

class MowCounterTransactions(object):
    def __init__(self):
        pass

    def add_sticker_request(self, user_id, file_id):
        pass

    def remove_sticker_request(self, file_id):
        pass

    def add_sticker(self, user_id, file_id, value):
        pass

    def remove_sticker(self, file_id):
        pass

    def get_stickers(self):
        pass

    def update_mow_count(self, user_id, group_id, mow_count):
        pass

    def reset_counts(self):
        pass

    def get_group_list(self):
        pass

    def get_own_count(self, user_id):
        pass

    def get_group_top10(self, group_id):
        pass

    def get_global_top10(self, group_id):
        pass

class MowRedisTransactions(MowCounterTransactions):
    def __init__(self):
        # self.redis = redis.StrictRedis(host='localhost',
        #                                db=0,
        #                                decode_responses=True)

    def add_sticker_request(self, user_id, file_id):
        self.redis.hmset("sticker-requests", {file_id: user_id})

    def remove_sticker_request(self, file_id):
        self.redis.hdel("sticker-requests", file_id)

    def get_sticker_requests(self):
        return self.redis.hgetall("sticker-requests")

    def get_stickers(self):
        return self.redis.hgetall("sticker-values")

    def get_sticker_value(self, sticker_id):
        v = self.redis.hget("sticker-values", sticker_id)
        if v is not None:
            return int(v)
        return None

    def get_user(self, user_id):
        return self.redis.hgetall(user_id)

    def add_sticker(self, user_id, file_id, value):
        self.redis.hset("sticker-values", file_id, value)
        self.redis.hset("sticker-users", file_id, user_id)

    def remove_sticker(self, file_id):
        self.redis.hdel("sticker-values", file_id)
        self.redis.hdel("sticker-users", file_id)

    def get_sticker_values(self):
        return self.redis.hgetall("sticker-values")

    def update_mow_count(self, user_id, user_username, user_fname,
                         user_lname, group_id, group_title, mow_count):
        user_id = str(user_id)
        group_id = str(group_id)
        self.redis.hmset(user_id,
                         {"username": user_username,
                          "first-name": user_fname,
                          "last-name": user_lname})
        self.redis.sadd("groups", group_id)
        self.redis.hmset(group_id, {"title": group_title})
        self.redis.zincrby("user-scores", user_id, mow_count)
        self.redis.zincrby(group_id + "-scores", user_id, mow_count)

    def get_group_list(self):
        return self.redis.smembers("groups")

    def get_group(self, group_id):
        return self.redis.hgetall(group_id)

    def add_group(self, group_id):
        self.redis.sadd("groups", group_id)

    def reset_counts(self):
        self.redis.delete("user-scores")
        groups = self.redis.smembers("groups")
        for g in groups:
            self.redis.delete(g + "-scores")

    def get_own_count(self, user_id, group_id):
        user_id = str(user_id)
        group_id = str(group_id)
        r = {"local_score": 0,
             "local_rank": 0,
             "local_total": 0,
             "global_score": 0,
             "global_rank": 0,
             "global_total": 0}

        # global score
        r["global_score"] = self.redis.zscore("user-scores", user_id)
        if r["global_score"] is None:
            return None
        # global rank
        r["global_rank"] = self.redis.zrevrank("user-scores", user_id)
        # global total
        r["global_total"] = self.redis.zcard("user-scores")

        # local score
        r["local_score"] = self.redis.zscore(group_id + "-scores", user_id)
        # local rank
        r["local_rank"] = self.redis.zrevrank(group_id + "-scores", user_id)
        # local total
        r["local_total"] = self.redis.zcard(group_id + "-scores")
        if not r["local_score"]:
            r["local_score"] = 0
            if not r["local_total"]:
                r["local_total"] = 0
            r["local_rank"] = r["local_total"]
        return r

    def get_group_top10(self, group_id):
        group_id = str(group_id)
        top10 = self.redis.zrevrange(group_id + "-scores", 0, 10,
                                     withscores=True, score_cast_func=int)
        top10list = []
        for (user_id, score) in top10:
            user_dict = {}
            u = self.redis.hgetall(user_id)
            user_dict["name"] = u["first-name"] + u["last-name"]
            user_dict["score"] = score
            top10list.append(user_dict)
        return top10list

    def get_global_top10(self, group_id):
        group_id = str(group_id)
        top10 = self.redis.zrevrange("user-scores", 0, 10,
                                     withscores=True, score_cast_func=int)
        top10list = []
        for (user_id, score) in top10:
            user_dict = {}
            u = self.redis.hgetall(user_id)
            user_dict["name"] = u["first-name"] + u["last-name"]
            user_dict["score"] = score
            top10list.append(user_dict)
        return top10list

class MowMySQLTransactions(MowCounterTransactions):
    pass

class MowCounter(MetafetishModuleBase):
    def __init__(self, dbdir, cm):
        super().__init__(__name__)
        self.store = MowRedisTransactions()
        self.cm = cm

    def rm_sticker(self, bot, update):
        sticker = None
        while True:
            bot.sendMessage(update.message.chat.id,
                            text="Send me the sticker you'd like to add, or /cancel.")
            (bot, update) = yield
            if update.message.sticker is not None:
                sticker = update.message.sticker
                break
            bot.sendMessage(update.message.chat.id,
                            text="That's not a sticker!")
        if sticker.file_id not in self.store.get_stickers().keys():
            bot.sendMessage(update.message.chat.id,
                            text="I don't know that sticker!")
            return
        self.store.remove_sticker(sticker.file_id)
        bot.sendMessage(update.message.chat.id,
                        text="Sticker removed!")

    def check_mows(self, bot, update):
        user = update.message.from_user
        chat = update.message.chat
        mows = 0
        # The API tests for text as either text or '', not None. God damnit.
        if len(update.message.text) is not 0:
            # count mows. Maximum one mow per message
            mows = 1 if update.message.text.lower().count("mow") else 0
        elif update.message.sticker is not None:
            sticker = update.message.sticker
            # Make sure we have the sticker and it's accepted
            value = self.store.get_sticker_value(sticker.file_id)
            if value is None:
                return
            mows = value
        if mows is 0:
            return
        self.store.update_mow_count(user.id, user.username, user.first_name,
                                    user.last_name, chat.id, chat.title, mows)

    def dump(self):
        pass

    def show_own_count(self, bot, update):
        user_id = str(update.message.from_user.id)
        chat_id = str(update.message.chat.id)
        user = update.message.from_user
        r = self.store.get_own_count(user_id, chat_id)
        if r is None:
            bot.sendMessage(update.message.chat.id,
                            text="%s %s has no mows!" % (user.first_name, user.last_name))
            return
        bot.sendMessage(update.message.chat.id,
                        text="%s %s has mowed %d times in this group (Rank: %d of %d), and %d times globally (Rank: %d of %d)." %
                        (user.first_name, user.last_name,
                         r["local_score"], r["local_rank"] + 1, r["local_total"],
                         r["global_score"], r["global_rank"] + 1, r["global_total"]))

    def show_top10_count(self, bot, update):
        chat_id = str(update.message.chat.id)
        group_top10 = self.store.get_group_top10(chat_id)
        global_top10 = self.store.get_global_top10(chat_id)
        msg = "Top 10 Count for Group '%s':\n\n" % update.message.chat.title
        i = 1
        for u in group_top10:
            msg += ("%d. " % (i)) + cgi.escape("%s - %s\n" % (u["name"], u["score"]))
            i += 1
        msg += "\nTop 10 Count Globally:\n\n"
        i = 1
        for u in global_top10:
            msg += ("%d. " % (i)) + cgi.escape("%s - %s\n" % (u["name"], u["score"]))
            i += 1
        bot.sendMessage(update.message.chat.id,
                        text=msg,
                        parse_mode="HTML")

    def list_groups(self, bot, update):
        group_names = []
        for g in self.store.get_group_list():
            try:
                chat = bot.getChat(g)
            except:
                print("Cannot access chat %s?" % g)
                continue
            status = bot.getChatMember(g, bot.id)
            group_names.append("%s - @%s - %s - %s" % (chat.title, chat.username, g, status.status))
        bot.sendMessage(update.message.chat.id,
                        text="Groups I am currently counting mows in:\n %s" % "\n".join(group_names))
        pass

    def list_stickers(self, bot, update):
        bot.sendMessage(update.message.chat.id,
                        text="Here's the list of stickers that count for/against mow counts:")
        for (k, v) in self.store.get_stickers().items():
            bot.sendSticker(update.message.chat.id,
                            k)
            bot.sendMessage(update.message.chat.id,
                            text="^^^^ Sticker Value: %d" % int(v))

    def request_sticker(self, bot, update):
        sticker = None
        while True:
            bot.sendMessage(update.message.chat.id,
                            text="Send me the sticker you'd like to request to be added, or /cancel.")
            (bot, update) = yield
            if update.message.sticker is not None:
                sticker = update.message.sticker
                break
            bot.sendMessage(update.message.chat.id,
                            text="That's not a sticker!")
        if sticker.file_id in self.store.get_stickers().keys() or sticker.file_id in self.store.get_sticker_requests().keys():
            bot.sendMessage(update.message.chat.id,
                            text="I'm already tracking or waiting to review that sticker!")
            return
        self.store.add_sticker_request(update.message.from_user.id,
                                       sticker.file_id)
        bot.sendMessage(update.message.chat.id,
                        text="Sticker requested! The admins will review the sticker and add it if it is mow-worthy. Thanks!")

    def review_stickers(self, bot, update):
        for (sticker_id, user_id) in self.store.get_sticker_requests().items():
            while True:
                bot.sendSticker(update.message.chat.id,
                                sticker_id)
                bot.sendMessage(update.message.chat.id,
                                text="Send 0 to reject sticker, or a pos/neg int to specify sticker value, or /cancel.")
                (bot, update) = yield
                try:
                    value = int(update.message.text)
                    if value != 0:
                        self.store.add_sticker(user_id, sticker_id, value)
                        bot.sendMessage(update.message.chat.id,
                                        text="Sticker added with value %d" % value)
                    self.store.remove_sticker_request(sticker_id)
                    break
                except:
                    continue
        bot.sendMessage(update.message.chat.id,
                        text="Sticker review done!")

    def reset(self, bot, update):
        self.store.reset_counts()
        bot.sendMessage(update.message.chat.id,
                        text="Mow counts reset!")

    def broadcast_message(self, bot, update):
        bot.sendMessage(update.message.chat.id,
                        text="What message would you like to broadcast to groups I'm in?")
        (bot, update) = yield
        message = update.message.text
        groups = self.store.get_group_list()
        for g in groups:
            bot.sendMessage(g,
                            text=message)
<|endoftext|>


<commit_before>"""Contains the ShowController base class."""

import logging
from queue import Queue

from mpf.core.utility_functions import Util
from mpf.assets.show import Show

class ShowController(object):
    """Manages all the shows in a pinball machine.

    'hardware shows' are coordinated light, flasher, coil, and event effects.
    The ShowController handles priorities, restores, running and stopping
    Shows, etc. There should be only one per machine.

    Args:
        machine: Parent machine object.
    """

    def __init__(self, machine):
        self.log = logging.getLogger("ShowController")
        self.machine = machine

        self.running_shows = list()
        self.registered_tick_handlers = set()
        self.current_tick_time = 0

        self.external_show_connected = False
        self.external_show_command_queue = Queue()
        """A thread-safe queue that receives BCP external show commands in
        the BCP worker
        thread. The light controller reads and processes these commands in
        the main
        thread via a tick handler.
        """
        self.running_external_show_keys = dict()
        """Dict of active external light shows that were created via BCP
        commands. This is
        useful for stopping shows later. Keys are based on the 'name'
        parameter specified
        when an external show was started, values are references to the
        external show object.
        """

        # TODO: this queue is not needed since its processed in the same thread. Part of #154
        # Setup the callback schedule (every frame)
        # self.machine.clock.schedule_interval(self._tick, 0)

        # Registers Show with the asset manager
        Show.initialize(self.machine)

        self.machine.events.add_handler('init_phase_3', self._initialize)

        self.machine.mode_controller.register_load_method(
            self._process_config_shows_section, 'shows')

    def _initialize(self):
        if 'shows' in self.machine.config:
            self._process_config_shows_section(self.machine.config['shows'])

    def _process_config_shows_section(self, config, **kwargs):
        # processes the shows: section of a mode or machine config
        del kwargs

        for show, settings in config.items():
            self.register_show(show, settings)

    def get_running_shows(self, name):
        """Returns a list of running shows by show name or instance name.

        Args:
            name: String name of the running shows you want to get. This can
                be a show name (which will return all running instances of that
                show) or a key (which will also return all running
                show instances that have that instance name).

        Returns:
            A list of RunningShow() objects.

        """

        return [x for x in self.running_shows if x.name == name]

    def register_show(self, name, settings):
        if name in self.machine.shows:
            raise ValueError("Show named '{}' was just registered, but "
                             "there's already a show with that name. Shows are"
                             " shared machine-wide".format(name))
        else:
            self.machine.shows[name] = Show(self.machine,
                                            name=name,
                                            data=settings,
                                            file=None)

    def unload_show_player_shows(self, removal_tuple):
        event_keys, shows = removal_tuple

        self.log.debug("Removing show_player events & stopping shows")
        self.machine.events.remove_handlers_by_keys(event_keys)

        for show in shows:
            try:
                show.stop()
            except AttributeError:
                pass

    def notify_show_starting(self, show):
        self.running_shows.append(show)
        self.running_shows.sort(key=lambda x: x.priority)

    def notify_show_stopping(self, show):
        self.running_shows.remove(show)

    def get_next_show_step(self):
        next_show_step_time = False
        for show in self.running_shows:
            if (not next_show_step_time or
                    show.next_step_time < next_show_step_time):
                next_show_step_time = show.next_step_time

        return next_show_step_time

    def register_tick_handler(self, handler):
        self.registered_tick_handlers.add(handler)

    def deregister_tick_handler(self, handler):
        if handler in self.registered_tick_handlers:
            self.registered_tick_handlers.remove(handler)

    def _tick(self, dt):
        # Runs once per machine frame. Calls the tick processing function
        # for any running shows
        # and processes any device updates and/or show actions that are needed.
        del dt
        self.current_tick_time = self.machine.clock.get_time()

        # Process the running Shows
        # for show in self.running_shows:
        #     show.tick(self.current_tick_time)

        for handler in self.registered_tick_handlers:
            handler()

    # pylint: disable-msg=too-many-arguments
    def add_external_show_start_command_to_queue(self, name, priority=0,
                                                 leds=None,
                                                 lights=None, flashers=None,
                                                 gis=None, coils=None):
        """Called by BCP worker thread when an external show start command
        is received
        via BCP.  Adds the command to a thread-safe queue where it will be
        processed
        by the main thread.

        Args:
            name: The name of the external show, used as a key for
            subsequent show commands.
            priority: Integer value of the relative priority of this show.
            leds: A list of led device names that will be used in this show.
            lights: A list of light device names that will be used in this
            show.
            flashers: A list of flasher device names that will be used in
            this show.
            gis: A list of GI device names that will be used in this show.
            coils: A list of coil device names that will be used in this show.
        """
        if not self.external_show_connected:
            self.register_tick_handler(
                self._update_external_shows)
            self.external_show_connected = True

        self.external_show_command_queue.put(
            (self._process_external_show_start_command,
             (name, priority, leds,
              lights, flashers, gis, coils)))

    def add_external_show_stop_command_to_queue(self, name):
        """Called by BCP worker thread when an external show stop command is
        received
        via BCP.  Adds the command to a thread-safe queue where it will be
        processed
        by the main thread.

        Args:
            name: The name of the external show.
        """
        self.external_show_command_queue.put(
            (self._process_external_show_stop_command, (name,)))

    # pylint: disable-msg=too-many-arguments
    def add_external_show_frame_command_to_queue(self, name, led_data=None,
                                                 light_data=None,
                                                 flasher_data=None,
                                                 gi_data=None,
                                                 coil_data=None, events=None):
        """Called by BCP worker thread when an external show frame command
        is received
        via BCP.  Adds the command to a thread-safe queue where it will be
        processed
        by the main thread.

        Args:
            name: The name of the external show.
            led_data: A string of concatenated hex color values for the leds
            in the show.
            light_data: A string of concatenated hex brightness values for
            the lights in the show.
            flasher_data: A string of concatenated pulse time (ms) values
            for the flashers in the show.
            gi_data: A string of concatenated hex brightness values for the
            GI in the show.
            coil_data: A string of concatenated coil values
            events: A comma-separated list of events to fire
        """
        self.external_show_command_queue.put(
            (self._process_external_show_frame_command,
             (name, led_data, light_data,
              flasher_data, gi_data, coil_data, events)))

    def _update_external_shows(self):
        """Processes any pending BCP external show commands.  This function
        is called
        in the main processing thread and is a tick handler function.
        """
        while not self.external_show_command_queue.empty():
            update_method, args = self.external_show_command_queue.get(False)
            update_method(*args)

    # pylint: disable-msg=too-many-arguments
    def _process_external_show_start_command(self, name, priority, leds,
                                             lights, flashers, gis, coils):
        """Processes an external show start command.  Runs in the main
        processing thread.

        Args:
            name: The name of the external show, used as a key for
            subsequent show commands.
            priority: Integer value of the relative priority of this show.
            leds: A list of led device names that will be used in this show.
            lights: A list of light device names that will be used in this
            show.
            flashers: A list of flasher device names that will be used in
            this show.
            gis: A list of GI device names that will be used in this show.
        """
        if name not in self.running_external_show_keys:
            self.running_external_show_keys[name] = ExternalShow(self.machine,
                                                                 name,
                                                                 priority,
                                                                 leds,
                                                                 lights,
                                                                 flashers, gis,
                                                                 coils)

    def _process_external_show_stop_command(self, name):
        """Processes an external show stop command.  Runs in the main
        processing thread.

        Args:
            name: The name of the external show.
        """
        try:
            self.running_external_show_keys[name].stop()
            del self.running_external_show_keys[name]
        except KeyError:
            pass

            # TODO: Would like to de-register the tick handler for external
            # shows here
            # However, since this function is called from the tick handler
            # while
            # iterating over the set of handlers, the list cannot be
            # modified at
            # this point in time.  Since this function is so quick,
            # it's probably
            # okay to leave it for now rather than figure out a complicated
            # way to
            # remove it.

            # def _process_external_show_frame_command(self, name, led_data, light_data,
            #                                          flasher_data, gi_data, coil_data,
            #                                          events):
            #     """Processes an external show frame command.  Runs in the main
            #     processing thread.
            #
            #     Args:
            #         name: The name of the external show.
            #         led_data: A string of concatenated hex color values for the leds
            #         in the show.
            #         light_data: A string of concatenated hex brightness values for
            #         the lights in the show.
            #         flasher_data: A string of concatenated pulse time (ms) values
            #         for the flashers in the show.
            #         gi_data: A string of concatenated hex brightness values for the
            #         GI in the show.
            #         coil_data: A string of concatenated coil values ????
            #         events: A comma-separated list of event names to fire immediately.
            #     """
            #     if name not in self.running_external_show_keys:
            #         return
            #
            #     if led_data:
            #         self.running_external_show_keys[name].update_leds(led_data)
            #
            #     if light_data:
            #         self.running_external_show_keys[name].update_lights(light_data)
            #
            #     if flasher_data:
            #         self.running_external_show_keys[name].update_gis(flasher_data)
            #
            #     if gi_data:
            #         self.running_external_show_keys[name].update_flashers(gi_data)
            #
            #     if coil_data:
            #         self.running_external_show_keys[name].update_coils(coil_data)
            #
            #     if events:
            #         for event in events.split(","):
            #             self.machine.show_controller._add_to_event_queue(event)

class ExternalShow(object):
    # pylint: disable-msg=too-many-arguments
    def __init__(self, machine, name, priority=0, leds=None,
                 lights=None, flashers=None, gis=None, coils=None):

        self.machine = machine
        self.name = name
        self.priority = priority
        self.name = None
        self.leds = list()
        self.lights = list()
        self.flashers = list()
        self.gis = list()
        self.coils = list()

        if leds:
            self.leds = [self.machine.leds[x] for x in
                         Util.string_to_list(leds)]

        if lights:
            self.lights = [self.machine.lights[x] for x in
                           Util.string_to_list(lights)]

        if flashers:
            self.flashers = [self.machine.flashers[x] for x in
                             Util.string_to_list(flashers)]

        if gis:
            self.gis = [self.machine.gis[x] for x in Util.string_to_list(gis)]

        if coils:
            self.coils = [self.machine.coils[x] for x in
                          Util.string_to_list(coils)]

    def stop(self):
        for led in self.leds:
            if led.cache['priority'] <= self.priority:
                led.restore()

        for light in self.lights:
            if light.cache['priority'] <= self.priority:
                light.restore()
<commit_msg>remove dead code<commit_after>"""Contains the ShowController base class."""

import logging

from mpf.assets.show import Show

class ShowController(object):
    """Manages all the shows in a pinball machine.

    'hardware shows' are coordinated light, flasher, coil, and event effects.
    The ShowController handles priorities, restores, running and stopping
    Shows, etc. There should be only one per machine.

    Args:
        machine: Parent machine object.
    """

    def __init__(self, machine):
        self.log = logging.getLogger("ShowController")
        self.machine = machine

        self.running_shows = list()

        # Registers Show with the asset manager
        Show.initialize(self.machine)

        self.machine.events.add_handler('init_phase_3', self._initialize)

        self.machine.mode_controller.register_load_method(
            self._process_config_shows_section, 'shows')

    def _initialize(self):
        if 'shows' in self.machine.config:
            self._process_config_shows_section(self.machine.config['shows'])

    def _process_config_shows_section(self, config, **kwargs):
        # processes the shows: section of a mode or machine config
        del kwargs

        for show, settings in config.items():
            self.register_show(show, settings)

    def get_running_shows(self, name):
        """Returns a list of running shows by show name or instance name.

        Args:
            name: String name of the running shows you want to get. This can
                be a show name (which will return all running instances of that
                show) or a key (which will also return all running
                show instances that have that instance name).

        Returns:
            A list of RunningShow() objects.

        """

        return [x for x in self.running_shows if x.name == name]

    def register_show(self, name, settings):
        if name in self.machine.shows:
            raise ValueError("Show named '{}' was just registered, but "
                             "there's already a show with that name. Shows are"
                             " shared machine-wide".format(name))
        else:
            self.machine.shows[name] = Show(self.machine,
                                            name=name,
                                            data=settings,
                                            file=None)

    def notify_show_starting(self, show):
        self.running_shows.append(show)
        self.running_shows.sort(key=lambda x: x.priority)

    def notify_show_stopping(self, show):
        self.running_shows.remove(show)
<|endoftext|>


<commit_before>from copy import deepcopy

import queue
import logging
from distutils.version import LooseVersion

import psutil
from kivy.clock import Clock

import mpf.core.bcp.bcp_socket_client as bcp
from mpfmc._version import __bcp_version__
from mpfmc.core.bcp_server import BCPServer

class BcpProcessor(object):
    def __init__(self, mc):
        self.mc = mc
        self.log = logging.getLogger('BcpProcessor')

        self.socket_thread = None
        self.connected = False
        self.receive_queue = queue.Queue()
        self.sending_queue = queue.Queue()
        self.mc_process = psutil.Process()

        if self.mc.options['bcp']:
            self.mc.events.add_handler('init_done', self._start_socket_thread)
            self.enabled = True
        else:
            self.enabled = False

        self.debug_log = self.mc.machine_config['bcp']['debug']

        self.bcp_commands = {'error': self._bcp_error,
                             'goodbye': self._bcp_goodbye,
                             'hello': self._bcp_hello,
                             'machine_variable': self._bcp_machine_variable,
                             'mode_start': self._bcp_mode_start,
                             'mode_stop': self._bcp_mode_stop,
                             'ball_start': self._bcp_ball_start,
                             'ball_end': self._bcp_ball_end,
                             'player_added': self._bcp_player_add,
                             'player_turn_start': self._bcp_player_turn_start,
                             'player_variable': self._bcp_player_variable,
                             'reset': self._bcp_reset,
                             'settings': self._bcp_settings,
                             'status_request': self._bcp_status_request,
                             'switch': self._bcp_switch,
                             'trigger': self._bcp_trigger,
                             }

        self.mc.events.add_handler('client_connected', self._client_connected)
        self.mc.events.add_handler('mc_reset_complete', self._reset_complete)

        Clock.schedule_interval(self._get_from_queue, 0)

    def _client_connected(self, **kwargs):
        del kwargs
        self.send(bcp_command="monitor_start", category="machine_vars")
        self.send(bcp_command="monitor_start", category="player_vars")
        self.send(bcp_command="monitor_start", category="modes")
        self.send(bcp_command="monitor_start", category="core_events")
        self.register_trigger("master_volume_increase")
        self.register_trigger("master_volume_decrease")
        self.register_trigger("debug_dump_stats")
        self.connected = True

    def register_trigger(self, event):
        """Register a trigger for events from MPF."""
        self.send("register_trigger", event=event)

    def _start_socket_thread(self, **kwargs):
        del kwargs

        if self.socket_thread:
            return

        self.socket_thread = BCPServer(self.mc, self.receive_queue,
                                       self.sending_queue)
        self.socket_thread.daemon = True
        self.socket_thread.start()

        self.mc.events.remove_handler(self._start_socket_thread)

    def send(self, bcp_command, callback=None, rawbytes=None, **kwargs):
        """Sends a BCP command to the connected pinball controller.

        Note that if the BCP server is not running, this method will just throw
        the BCP away. (It will still call the callback in that case.

        Args:
            bcp_command: String of the BCP command name.
            callback: Optional callback method that will be called when the
                command is sent.
            **kwargs: Optional additional kwargs will be added to the BCP
                command string.

        """
        if self.enabled:
            if not self.mc.bcp_client_connected:
                raise AssertionError("Not connected to MPF.")

            self.sending_queue.put(
                (bcp.encode_command_string(bcp_command, **kwargs), rawbytes))

        if callback:
            callback()

    def receive_bcp_message(self, msg):
        """Receives an incoming BCP message to be processed.

        Note this method is intended for testing. Usually BCP messages are
        handled by the BCP Server thread, but for test purposes it's possible
        to run mpf-mc without the BCP Server, so in that case you can use this
        method to send BCP messages into the mpf-mc.

        Args:
            msg: A string of the BCP message (in the standard BCP format:
                command?param1=value1&param2=value2...

        """
        cmd, kwargs = bcp.decode_command_string(msg)
        self.receive_queue.put((cmd, kwargs))

    def _get_from_queue(self, dt):
        """Gets and processes all queued up incoming BCP commands."""
        del dt

        while not self.receive_queue.empty():
            cmd, kwargs = self.receive_queue.get(False)
            self._process_command(cmd, **kwargs)

    def _process_command(self, bcp_command, **kwargs):
        if self.debug_log:
            if 'rawbytes' in kwargs:
                debug_kwargs = deepcopy(kwargs)
                debug_kwargs['rawbytes'] = '<{} bytes>'.format(
                    len(debug_kwargs.pop('rawbytes')))

                self.log.debug("Processing command: %s %s", bcp_command,
                               debug_kwargs)
            else:
                self.log.debug("Processing command: %s %s", bcp_command,
                               kwargs)

        # Can't use try/except KeyError here because there could be a KeyError
        # in the callback which we don't want it to swallow.
        if bcp_command in self.bcp_commands:
            self.bcp_commands[bcp_command](**kwargs)
        else:
            self.log.warning("Received invalid BCP command: %s", bcp_command[:9])
            # self.send('error',message='invalid command', command=bcp_command)

    def _bcp_status_request(self, **kwargs):
        """Status request."""
        del kwargs

        self.send("status_report",
                  cpu=self.mc_process.cpu_percent(),
                  rss=self.mc_process.memory_info().rss,
                  vms=self.mc_process.memory_info().vms)

    def _bcp_hello(self, **kwargs):
        """Processes an incoming BCP 'hello' command."""
        try:
            if LooseVersion(kwargs['version']) == (
                    LooseVersion(__bcp_version__)):
                self.send('hello', version=__bcp_version__)
            else:
                self.send('hello', version='unknown protocol version')
        except KeyError:
            self.log.warning("Received invalid 'version' parameter with 'hello'")

    def _bcp_goodbye(self, **kwargs):
        """Processes an incoming BCP 'goodbye' command."""
        # if self.config['mpf-mc']['exit_on_disconnect']:
        #     self.socket_thread.sending_thread.stop()
        #     sys.exit()

        pass  # todo

    def _bcp_mode_start(self, name=None, priority=0, **kwargs):
        """Processes an incoming BCP 'mode_start' command."""
        del kwargs

        if not name:
            return
            # todo raise error

        if name == 'game':
            self.mc.game_start()

        if name in self.mc.modes:
            self.mc.modes[name].start(mode_priority=int(priority))

    def _bcp_mode_stop(self, name, **kwargs):
        """Processes an incoming BCP 'mode_stop' command."""
        del kwargs
        if not name:
            return
            # todo raise error

        if name == 'game':
            self.mc.game_end()

        if name in self.mc.modes:
            self.mc.modes[name].stop()

    def _bcp_ball_start(self, player_num, ball, **kwargs):
        del player_num, ball, kwargs

    def _bcp_ball_end(self, **kwargs):
        del kwargs

    def _bcp_settings(self, settings, **kwargs):
        del kwargs
        for setting in settings:
            self.mc.settings.add_setting(setting)

    def _bcp_error(self, **kwargs):
        """Processes an incoming BCP 'error' command."""
        del kwargs
        self.log.warning('Received error command from client')

    def _bcp_player_add(self, player_num, **kwargs):
        """Processes an incoming BCP 'player_add' command."""
        del kwargs
        self.mc.add_player(int(player_num))

    # pylint: disable-msg=too-many-arguments
    def _bcp_player_variable(self, name, value, prev_value, change, player_num,
                             **kwargs):
        """Processes an incoming BCP 'player_variable' command."""
        del prev_value
        del change
        del kwargs
        self.mc.update_player_var(name, value, int(player_num))

    def send_machine_var_to_mpf(self, name, value):
        """Set machine var in MPF via BCP."""
        self.send("set_machine_var", name=name, value=value)

    def _bcp_machine_variable(self, name, value, change=True, prev_value=None,
                              **kwargs):
        """Processes an incoming BCP 'machine_variable' command."""
        del kwargs
        self.mc.receive_machine_var_update(name, value, change, prev_value)

    def _bcp_player_turn_start(self, player_num, **kwargs):
        """Processes an incoming BCP 'player_turn_start' command."""
        del kwargs
        self.mc.player_start_turn(int(player_num))

    def _bcp_trigger(self, name, **kwargs):
        """Processes an incoming BCP 'trigger' command."""
        self.mc.events.post(name, **kwargs)

    def _bcp_switch(self, name, state, **kwargs):
        """Processes an incoming BCP 'switch' command."""
        del kwargs
        if int(state):
            self.mc.events.post('switch_' + name + '_active')
            '''event: switch_(name)_active
            desc: Posted on MPF-MC only (e.g. not in MPF) when the MC receives
            a BCP "switch" active command. Useful for video modes and graphical
            menu navigation. Note that this is not posted for every switch all
            the time, rather, only for switches that have been configured to
            send events to BCP.
            '''
        else:
            self.mc.events.post('switch_' + name + '_inactive')
            '''event: switch_(name)_inactive
            desc: Posted on MPF-MC only (e.g. not in MPF) when the MC receives
            a BCP "switch" inactive command. Useful for video modes and graphical
            menu navigation. Note that this is not posted for every switch all
            the time, rather, only for switches that have been configured to
            send events to BCP.
            '''

    def _bcp_reset(self, **kwargs):
        del kwargs
        self.mc.reset()

    def _reset_complete(self, **kwargs):
        del kwargs
        self.send('reset_complete')
<commit_msg>Add BCP monitor status_request to startup<commit_after>from copy import deepcopy

import queue
import logging
from distutils.version import LooseVersion

import psutil
from kivy.clock import Clock

import mpf.core.bcp.bcp_socket_client as bcp
from mpfmc._version import __bcp_version__
from mpfmc.core.bcp_server import BCPServer

class BcpProcessor(object):
    def __init__(self, mc):
        self.mc = mc
        self.log = logging.getLogger('BcpProcessor')

        self.socket_thread = None
        self.connected = False
        self.receive_queue = queue.Queue()
        self.sending_queue = queue.Queue()
        self.mc_process = psutil.Process()

        if self.mc.options['bcp']:
            self.mc.events.add_handler('init_done', self._start_socket_thread)
            self.enabled = True
        else:
            self.enabled = False

        self.debug_log = self.mc.machine_config['bcp']['debug']

        self.bcp_commands = {'error': self._bcp_error,
                             'goodbye': self._bcp_goodbye,
                             'hello': self._bcp_hello,
                             'machine_variable': self._bcp_machine_variable,
                             'mode_start': self._bcp_mode_start,
                             'mode_stop': self._bcp_mode_stop,
                             'ball_start': self._bcp_ball_start,
                             'ball_end': self._bcp_ball_end,
                             'player_added': self._bcp_player_add,
                             'player_turn_start': self._bcp_player_turn_start,
                             'player_variable': self._bcp_player_variable,
                             'reset': self._bcp_reset,
                             'settings': self._bcp_settings,
                             'status_request': self._bcp_status_request,
                             'switch': self._bcp_switch,
                             'trigger': self._bcp_trigger,
                             }

        self.mc.events.add_handler('client_connected', self._client_connected)
        self.mc.events.add_handler('mc_reset_complete', self._reset_complete)

        Clock.schedule_interval(self._get_from_queue, 0)

    def _client_connected(self, **kwargs):
        del kwargs
        self.send(bcp_command="monitor_start", category="machine_vars")
        self.send(bcp_command="monitor_start", category="player_vars")
        self.send(bcp_command="monitor_start", category="modes")
        self.send(bcp_command="monitor_start", category="core_events")
        self.send(bcp_command="monitor_start", category="status_request")
        self.register_trigger("master_volume_increase")
        self.register_trigger("master_volume_decrease")
        self.register_trigger("debug_dump_stats")
        self.connected = True

    def register_trigger(self, event):
        """Register a trigger for events from MPF."""
        self.send("register_trigger", event=event)

    def _start_socket_thread(self, **kwargs):
        del kwargs

        if self.socket_thread:
            return

        self.socket_thread = BCPServer(self.mc, self.receive_queue,
                                       self.sending_queue)
        self.socket_thread.daemon = True
        self.socket_thread.start()

        self.mc.events.remove_handler(self._start_socket_thread)

    def send(self, bcp_command, callback=None, rawbytes=None, **kwargs):
        """Sends a BCP command to the connected pinball controller.

        Note that if the BCP server is not running, this method will just throw
        the BCP away. (It will still call the callback in that case.

        Args:
            bcp_command: String of the BCP command name.
            callback: Optional callback method that will be called when the
                command is sent.
            **kwargs: Optional additional kwargs will be added to the BCP
                command string.

        """
        if self.enabled:
            if not self.mc.bcp_client_connected:
                raise AssertionError("Not connected to MPF.")

            self.sending_queue.put(
                (bcp.encode_command_string(bcp_command, **kwargs), rawbytes))

        if callback:
            callback()

    def receive_bcp_message(self, msg):
        """Receives an incoming BCP message to be processed.

        Note this method is intended for testing. Usually BCP messages are
        handled by the BCP Server thread, but for test purposes it's possible
        to run mpf-mc without the BCP Server, so in that case you can use this
        method to send BCP messages into the mpf-mc.

        Args:
            msg: A string of the BCP message (in the standard BCP format:
                command?param1=value1&param2=value2...

        """
        cmd, kwargs = bcp.decode_command_string(msg)
        self.receive_queue.put((cmd, kwargs))

    def _get_from_queue(self, dt):
        """Gets and processes all queued up incoming BCP commands."""
        del dt

        while not self.receive_queue.empty():
            cmd, kwargs = self.receive_queue.get(False)
            self._process_command(cmd, **kwargs)

    def _process_command(self, bcp_command, **kwargs):
        if self.debug_log:
            if 'rawbytes' in kwargs:
                debug_kwargs = deepcopy(kwargs)
                debug_kwargs['rawbytes'] = '<{} bytes>'.format(
                    len(debug_kwargs.pop('rawbytes')))

                self.log.debug("Processing command: %s %s", bcp_command,
                               debug_kwargs)
            else:
                self.log.debug("Processing command: %s %s", bcp_command,
                               kwargs)

        # Can't use try/except KeyError here because there could be a KeyError
        # in the callback which we don't want it to swallow.
        if bcp_command in self.bcp_commands:
            self.bcp_commands[bcp_command](**kwargs)
        else:
            self.log.warning("Received invalid BCP command: %s", bcp_command[:9])
            # self.send('error',message='invalid command', command=bcp_command)

    def _bcp_status_request(self, **kwargs):
        """Status request."""
        del kwargs

        self.send("status_report",
                  cpu=self.mc_process.cpu_percent(),
                  rss=self.mc_process.memory_info().rss,
                  vms=self.mc_process.memory_info().vms)

    def _bcp_hello(self, **kwargs):
        """Processes an incoming BCP 'hello' command."""
        try:
            if LooseVersion(kwargs['version']) == (
                    LooseVersion(__bcp_version__)):
                self.send('hello', version=__bcp_version__)
            else:
                self.send('hello', version='unknown protocol version')
        except KeyError:
            self.log.warning("Received invalid 'version' parameter with 'hello'")

    def _bcp_goodbye(self, **kwargs):
        """Processes an incoming BCP 'goodbye' command."""
        # if self.config['mpf-mc']['exit_on_disconnect']:
        #     self.socket_thread.sending_thread.stop()
        #     sys.exit()

        pass  # todo

    def _bcp_mode_start(self, name=None, priority=0, **kwargs):
        """Processes an incoming BCP 'mode_start' command."""
        del kwargs

        if not name:
            return
            # todo raise error

        if name == 'game':
            self.mc.game_start()

        if name in self.mc.modes:
            self.mc.modes[name].start(mode_priority=int(priority))

    def _bcp_mode_stop(self, name, **kwargs):
        """Processes an incoming BCP 'mode_stop' command."""
        del kwargs
        if not name:
            return
            # todo raise error

        if name == 'game':
            self.mc.game_end()

        if name in self.mc.modes:
            self.mc.modes[name].stop()

    def _bcp_ball_start(self, player_num, ball, **kwargs):
        del player_num, ball, kwargs

    def _bcp_ball_end(self, **kwargs):
        del kwargs

    def _bcp_settings(self, settings, **kwargs):
        del kwargs
        for setting in settings:
            self.mc.settings.add_setting(setting)

    def _bcp_error(self, **kwargs):
        """Processes an incoming BCP 'error' command."""
        del kwargs
        self.log.warning('Received error command from client')

    def _bcp_player_add(self, player_num, **kwargs):
        """Processes an incoming BCP 'player_add' command."""
        del kwargs
        self.mc.add_player(int(player_num))

    # pylint: disable-msg=too-many-arguments
    def _bcp_player_variable(self, name, value, prev_value, change, player_num,
                             **kwargs):
        """Processes an incoming BCP 'player_variable' command."""
        del prev_value
        del change
        del kwargs
        self.mc.update_player_var(name, value, int(player_num))

    def send_machine_var_to_mpf(self, name, value):
        """Set machine var in MPF via BCP."""
        self.send("set_machine_var", name=name, value=value)

    def _bcp_machine_variable(self, name, value, change=True, prev_value=None,
                              **kwargs):
        """Processes an incoming BCP 'machine_variable' command."""
        del kwargs
        self.mc.receive_machine_var_update(name, value, change, prev_value)

    def _bcp_player_turn_start(self, player_num, **kwargs):
        """Processes an incoming BCP 'player_turn_start' command."""
        del kwargs
        self.mc.player_start_turn(int(player_num))

    def _bcp_trigger(self, name, **kwargs):
        """Processes an incoming BCP 'trigger' command."""
        self.mc.events.post(name, **kwargs)

    def _bcp_switch(self, name, state, **kwargs):
        """Processes an incoming BCP 'switch' command."""
        del kwargs
        if int(state):
            self.mc.events.post('switch_' + name + '_active')
            '''event: switch_(name)_active
            desc: Posted on MPF-MC only (e.g. not in MPF) when the MC receives
            a BCP "switch" active command. Useful for video modes and graphical
            menu navigation. Note that this is not posted for every switch all
            the time, rather, only for switches that have been configured to
            send events to BCP.
            '''
        else:
            self.mc.events.post('switch_' + name + '_inactive')
            '''event: switch_(name)_inactive
            desc: Posted on MPF-MC only (e.g. not in MPF) when the MC receives
            a BCP "switch" inactive command. Useful for video modes and graphical
            menu navigation. Note that this is not posted for every switch all
            the time, rather, only for switches that have been configured to
            send events to BCP.
            '''

    def _bcp_reset(self, **kwargs):
        del kwargs
        self.mc.reset()

    def _reset_complete(self, **kwargs):
        del kwargs
        self.send('reset_complete')
<|endoftext|>


<commit_before>import pandas as pd
import numpy as np
import logging

import Pulses
import C_Funct
import Utilities

def candidates(pulses,idL):
  pulses.sort(['Sigma','Pulse'],ascending=[0,1],inplace=True)

  pulses.Candidate = Repeated_candidates_beam(pulses).astype(pulses.Candidate.dtype)

  cands_unique = pulses[(pulses.Candidate==-1)&(pulses.Sigma>=10)].groupby(['SAP','BEAM'],sort=False)[['SAP','BEAM']].head(5).astype(np.int32)
  pulses.Candidate.loc[cands_unique.index.get_level_values('idx')] = np.arange(cands_unique.shape[0]) * 1000 + cands_unique.SAP * 100 + cands_unique.BEAM

  if not pulses[pulses.Candidate>=0].empty:
    cands = candidates_generator(pulses[pulses.Candidate>=0].copy(),idL)
    cands['main_cand'] = 0

    #Unify the same repeated candidates in different beams
    cands.sort(['Sigma','Rank'],ascending=[1,0],inplace=True)
    new_cand = cands.index

    C_Funct.Compare_candidates(cands.DM.astype(np.float32).values,cands.Time.astype(np.float32).values,cands.index.values,cands.main_cand.values)

    #selezionare solo top 2 signle candidates

  else: cands = pd.DataFrame()

  return cands

def Repeated_candidates_beam(pulses):
  gb_puls = pulses.groupby(['SAP','BEAM'],sort=False)
  dirs = [n for n in gb_puls.indices.iterkeys()]
  pulses['cand'] = -1

  for (sap,beam) in dirs:
    puls = pulses[(pulses.SAP==sap)&(pulses.BEAM==beam)].copy()
    puls.DM = 3*(puls.DM.astype(np.float64)/3).round(2)

    span = 0.25

    top_count = puls.groupby('DM')['Sigma'].count()
    top_sum = puls.groupby('DM')['Sigma'].sum()

    top_sum = top_sum[top_count >= 2]
    #top_count = top_count[top_count >= 2]

    i = 1

    while not top_sum[top_sum!=0].empty:
      DM = top_sum.argmax()
      #Sigma = top_sum.loc[DM-span:DM+span].sum()
      #N_puls = top_count.loc[DM-span:DM+span].sum()
      selected_pulses = puls.cand[(puls.DM>=DM-span)&(puls.DM<=DM+span)]
      if selected_pulses.shape[0] > 1:
        pulses.cand.loc[selected_pulses.index] = i * 100000 + sap * 1000 + beam * 10
      #top_count.loc[DM-span:DM+span] = 0
      top_sum.loc[DM-span:DM+span] = 0
      i += 1

  return pulses.cand

def period(x):
  if x.size<=1: return 0
  else: return Utilities.rrat_period(x)[0]

def period_err(x):
  if x.size<=1: return 0
  else: return Utilities.rrat_period(x)[1]

def candidates_generator(pulses,idL):
  pulses['Period'] = pulses.Time
  pulses['Period_err'] = pulses.Time

  cands = pulses.groupby(['Candidate','SAP','BEAM'],as_index=False,sort=False).agg({'Sigma':np.sum,'N_events':np.size,'DM':np.mean,'Time':np.min,'Period':period,'Period_err':period_err,'Pulse':np.max})

  cands = cands.astype(np.float32)
  cands[['N_events','Pulse','SAP','BEAM']] = cands[['N_events','Pulse','SAP','BEAM']].astype(np.int16)

  cands.index = cands.Candidate.astype(int)
  cands.index.name = 'idx'
  cands.rename(columns={'N_events': 'N_pulses', 'Pulse': 'Rank'}, inplace=True)
  cands = cands.drop('Candidate',axis=1)
  cands.Time[cands.N_pulses>1] = 0
  cands['id'] = idL + '_' + cands.SAP.astype(str) + '_' + cands.BEAM.astype(str) + '_' + cands.index.astype(str)

  cands.sort(['Sigma','Rank'],ascending=[0,1],inplace=True)
  best_cands = cands[cands.N_pulses==1].groupby('SAP').head(4)
  best_cands = best_cands.append(cands[cands.N_pulses>1].groupby('BEAM').head(2).groupby('SAP').head(4))

  return best_cands
<commit_msg>Candidates ID even/odd for unique/repeated<commit_after>import pandas as pd
import numpy as np
import logging

import Pulses
import C_Funct
import Utilities

def candidates(pulses,idL):
  pulses.sort(['Sigma','Pulse'],ascending=[0,1],inplace=True)

  pulses.Candidate = Repeated_candidates_beam(pulses).astype(pulses.Candidate.dtype)

  cands_unique = pulses[(pulses.Candidate==-1)&(pulses.Sigma>=10)].groupby(['SAP','BEAM'],sort=False)[['SAP','BEAM']].head(5).astype(np.int32)
  pulses.Candidate.loc[cands_unique.index.get_level_values('idx')] = 2 * (np.arange(cands_unique.shape[0]) * 10000 + cands_unique.SAP * 1000 + cands_unique.BEAM)
  #Unique candidates have even ID

  if not pulses[pulses.Candidate>=0].empty:
    cands = candidates_generator(pulses[pulses.Candidate>=0].copy(),idL)
    cands['main_cand'] = 0

    #Unify the same repeated candidates in different beams
    cands.sort(['Sigma','Rank'],ascending=[1,0],inplace=True)
    new_cand = cands.index

    C_Funct.Compare_candidates(cands.DM.astype(np.float32).values,cands.Time.astype(np.float32).values,cands.index.values,cands.main_cand.values)

    #selezionare solo top 2 signle candidates

  else: cands = pd.DataFrame()

  return cands

def Repeated_candidates_beam(pulses):
  gb_puls = pulses.groupby(['SAP','BEAM'],sort=False)
  dirs = [n for n in gb_puls.indices.iterkeys()]
  pulses['cand'] = -1

  for (sap,beam) in dirs:
    puls = pulses[(pulses.SAP==sap)&(pulses.BEAM==beam)].copy()
    puls.DM = 3*(puls.DM.astype(np.float64)/3).round(2)

    span = 0.25

    top_count = puls.groupby('DM')['Sigma'].count()
    top_sum = puls.groupby('DM')['Sigma'].sum()

    top_sum = top_sum[top_count >= 2]
    #top_count = top_count[top_count >= 2]

    i = 1

    while not top_sum[top_sum!=0].empty:
      DM = top_sum.argmax()
      #Sigma = top_sum.loc[DM-span:DM+span].sum()
      #N_puls = top_count.loc[DM-span:DM+span].sum()
      selected_pulses = puls.cand[(puls.DM>=DM-span)&(puls.DM<=DM+span)]
      if selected_pulses.shape[0] > 1:
        pulses.cand.loc[selected_pulses.index] = 1 + 2 * (i * 10000 + sap * 1000 + beam)  #Repeated candidates have odd ID
      #top_count.loc[DM-span:DM+span] = 0
      top_sum.loc[DM-span:DM+span] = 0
      i += 1

  return pulses.cand

def period(x):
  if x.size<=1: return 0
  else: return Utilities.rrat_period(x)[0]

def period_err(x):
  if x.size<=1: return 0
  else: return Utilities.rrat_period(x)[1]

def candidates_generator(pulses,idL):
  pulses['Period'] = pulses.Time
  pulses['Period_err'] = pulses.Time

  cands = pulses.groupby(['Candidate','SAP','BEAM'],as_index=False,sort=False).agg({'Sigma':np.sum,'N_events':np.size,'DM':np.mean,'Time':np.min,'Period':period,'Period_err':period_err,'Pulse':np.max})

  cands = cands.astype(np.float32)
  cands[['N_events','Pulse','SAP','BEAM']] = cands[['N_events','Pulse','SAP','BEAM']].astype(np.int16)

  cands.index = cands.Candidate.astype(int)
  cands.index.name = 'idx'
  cands.rename(columns={'N_events': 'N_pulses', 'Pulse': 'Rank'}, inplace=True)
  cands = cands.drop('Candidate',axis=1)
  cands.Time[cands.N_pulses>1] = 0
  cands['id'] = idL + '_' + cands.SAP.astype(str) + '_' + cands.BEAM.astype(str) + '_' + cands.index.astype(str)

  cands.sort(['Sigma','Rank'],ascending=[0,1],inplace=True)
  best_cands = cands[cands.N_pulses==1].groupby('SAP').head(4)
  best_cands = best_cands.append(cands[cands.N_pulses>1].groupby('BEAM').head(2).groupby('SAP').head(4))

  return best_cands
<|endoftext|>