args = parser.parse_args()
    logging.basicConfig(level=args.loglevel, format='%(asctime)s %(message)s')

    commandQueue = multiprocessing.Queue()
    reporterValuesQueue = multiprocessing.Queue()
    completedCommandsQueue = multiprocessing.Queue()

    server = WebsocketServer(WEBSOCKETS_PORT_NUMBER)
    server.reporterValues = {}
    server.reporterValuesQueue = reporterValuesQueue
    server.completedCommandsQueue = completedCommandsQueue
    server.set_fn_new_client(new_client)
    server.set_fn_client_left(client_left)
    server.set_fn_message_received(message_received)

    if args.mode == MODE_SIMULATOR:
        httpRobotSimulator = JSRobotSimulator(commandQueue, reporterValuesQueue, completedCommandsQueue)
    elif args.mode == MODE_LIVE:
        robot = py_websockets_bot.WebsocketsBot( args.remoteRobotHostname )
        httpRobotSimulator = JSRobotController(commandQueue, reporterValuesQueue, completedCommandsQueue, robot)
    else:
        raise "Unrecognised mode"

    server.httpRobotSimulator = httpRobotSimulator
    httpRobotSimulator.start()
    server.timeout = 0.1 #allows us to check if any commands have completed
    logging.info("Server is listening ")
    while 1:
        check_completed_commands(server)
        server.handle_request()
Esempio n. 2
0
class Plugin(indigo.PluginBase):

    ########################################
    # Main Plugin methods
    ########################################
    def __init__(self, pluginId, pluginDisplayName, pluginVersion,
                 pluginPrefs):
        indigo.PluginBase.__init__(self, pluginId, pluginDisplayName,
                                   pluginVersion, pluginPrefs)

        pfmt = logging.Formatter(
            '%(asctime)s.%(msecs)03d\t[%(levelname)8s] %(name)20s.%(funcName)-25s%(msg)s',
            datefmt='%Y-%m-%d %H:%M:%S')
        self.plugin_file_handler.setFormatter(pfmt)

        try:
            self.logLevel = int(self.pluginPrefs[u"logLevel"])
        except:
            self.logLevel = logging.INFO
        self.indigo_log_handler.setLevel(self.logLevel)

    def validatePrefsConfigUi(self, valuesDict):
        errorDict = indigo.Dict()

        try:
            self.logLevel = int(valuesDict[u"logLevel"])
        except:
            self.logLevel = logging.INFO
        self.indigo_log_handler.setLevel(self.logLevel)

        try:
            port = int(valuesDict[u"socketPort"])
        except:
            errorDict[u"socketPort"] = "Non-integer port number"
        else:
            if port < 1024:
                errorDict[u"socketPort"] = "Priviledged port number"

        if len(errorDict) > 0:
            return (False, valuesDict, errorDict)
        return (True, valuesDict)

    def closedPrefsConfigUi(self, valuesDict, userCancelled):
        if userCancelled:
            return

        self.logLevel = int(valuesDict[u"logLevel"])
        self.indigo_log_handler.setLevel(self.logLevel)

        if valuesDict[u"socketPort"] != self.socketPort:
            self.socketPort = valuesDict[u"socketPort"]
            self.start_websocket(int(self.socketPort))

    def startup(self):
        self.logger.info(u"Starting StreamDeck")
        self.triggers = {}
        self.activeConnections = {}

        # these are the Streamdeck devices and buttons that have Indigo devices to match
        self.activeDecks = {}
        self.activeButtons = {}

        # these are the known Streamdeck devices and buttons, may not have a matching Indigo device
        self.known_devices = indigo.activePlugin.pluginPrefs.get(
            u"known_devices", indigo.Dict())
        self.known_buttons = indigo.activePlugin.pluginPrefs.get(
            u"known_buttons", indigo.Dict())

        self.wsServer = None
        self.socketPort = self.pluginPrefs.get(u'socketPort', 9001)
        self.start_websocket(int(self.socketPort))

    def shutdown(self):
        self.logger.info(u"Stopping StreamDeck")
        indigo.activePlugin.pluginPrefs[u"known_devices"] = self.known_devices
        indigo.activePlugin.pluginPrefs[u"known_buttons"] = self.known_buttons

        if self.wsServer:
            self.wsServer.server_close()
            self.wsServer = None

    def start_websocket(self, port):
        if self.wsServer:
            self.logger.debug("Closing existing Websocket Server")
            self.wsServer.server_close()
            self.wsServer = None
        try:
            self.wsServer = WebsocketServer(
                port, '0.0.0.0')  # bind socket to all interfaces
            self.wsServer.set_fn_new_client(self.onConnect)
            self.wsServer.set_fn_client_left(self.onClose)
            self.wsServer.set_fn_message_received(self.onMessage)
            self.wsServer.timeout = 1.0
            self.logger.debug(
                u"Started Websocket Server on port {}".format(port))
        except Error as e:
            self.logger.warning(
                u"Error starting Websocket Server: {}".format(e))

    def runConcurrentThread(self):
        try:
            while True:
                if self.wsServer:
                    self.wsServer.handle_request()
                    self.sleep(0.1)
                else:
                    self.sleep(1)
        except self.StopThread:
            pass

    ####################

    def onConnect(self, client, server):
        self.logger.debug(u"onConnect client: {}".format(client['id']))
        self.activeConnections[client['id']] = client
        self.logger.debug(u"onConnect activeConnections: {}".format(
            self.activeConnections))

        reply = {"event": "connected", "clientID": client['id']}
        self.wsServer.send_message(client, json.dumps(reply))

    def onClose(self, client, server):
        self.logger.debug(u"onClose client: {}".format(client['id']))
        del self.activeConnections[client['id']]

    def onMessage(self, client, server, received):

        try:
            message = json.loads(received)
        except:
            self.logger.warning(
                u"onMessage from client {}, invalid JSON:\n{}".format(
                    client['id'], received))
            return

        self.logger.threaddebug(
            u"onMessage from client: {}, message: {}".format(
                client['id'], message))

        if not 'message-type' in message:
            self.logger.warning(
                "onMessage from client {}, no message-type, message: {}".
                format(client['id'], message))
            return

        if message['message-type'] == 'applicationInfo':
            self.applicationInfo(message, client)

        elif message['message-type'] in ['deviceDidConnect']:
            self.deviceDidConnect(message, client)

        elif message['message-type'] in ['deviceDidDisconnect']:
            self.deviceDidDisconnect(message, client)

        elif message['message-type'] in ['willAppear']:
            self.buttonWillAppear(message, client)

        elif message['message-type'] in ['willDisappear']:
            self.buttonWillDisappear(message, client)

        elif message['message-type'] in ['keyUp', 'keyDown']:
            self.keyPress(message, client)

    ####################

    def applicationInfo(self, message, client):
        for dev in message[u'payload'][u'devices']:
            self.logger.debug(
                u"onMessage applicationInfo Adding/Updating device: {}".format(
                    dev))

            deck = {}
            deck[u'id'] = dev[u'id']
            deck[u'name'] = dev[u'name']
            deck[u'columns'] = dev[u'size'][u'columns']
            deck[u'rows'] = dev[u'size'][u'rows']
            deck[u'type'] = dev.get(u'type', -1)
            deck[u'client'] = client['id']
            self.registerDeck(deck)

    def deviceDidConnect(self, message, client):
        self.logger.debug(
            u"onMessage deviceDidConnect Adding/Updating device: {}".format(
                message))

        deck = {}
        deck[u'id'] = message[u'payload'][u'device']
        deck[u'name'] = message[u'payload'][u'deviceInfo'][u'name']
        deck[u'columns'] = message[u'payload'][u'deviceInfo'][u'size'][
            u'columns']
        deck[u'rows'] = message[u'payload'][u'deviceInfo'][u'size'][u'rows']
        deck[u'type'] = message[u'payload'][u'deviceInfo'].get(u'type', -1)
        deck[u'client'] = client['id']
        self.registerDeck(deck)

    def registerDeck(self, deckDict):
        deckKey = safeKey(deckDict[u'id'])
        self.known_devices[deckKey] = deckDict

        if deckKey in self.activeDecks:  # If there's an active Indigo device, update the active state
            deckDevice = indigo.devices.get(int(self.activeDecks[deckKey]))
            if not deckDevice:
                self.logger.warning(
                    "registerDeck invalid Deck DeviceID: {}".format(deckDict))
            else:
                states_list = []
                states_list.append({'key': 'active', 'value': True})
                states_list.append({'key': 'name', 'value': deckDict[u'name']})
                states_list.append({
                    'key': 'columns',
                    'value': deckDict[u'columns']
                })
                states_list.append({'key': 'rows', 'value': deckDict[u'rows']})
                states_list.append({'key': 'type', 'value': deckDict[u'type']})
                states_list.append({
                    'key': 'clientID',
                    'value': deckDict['client']
                })
                deckDevice.updateStatesOnServer(states_list)

    def deviceDidDisconnect(self, message, client):
        self.logger.debug(u"onMessage deviceDidDisconnect: {}".format(message))
        deckKey = safeKey(message[u'payload'][u'device'])
        if deckKey in self.activeDecks:  # If there's an active Indigo device, update the active state
            deckDevice = indigo.devices.get(int(self.activeDecks[deckKey]))
            if not deckDevice:
                self.logger.warning(
                    "deviceDidDisconnect invalid Deck DeviceID: {}".format(
                        message))
            else:
                deckDevice.updateStateOnServer(key="active", value=False)

    def buttonWillAppear(self, message, client):
        self.logger.debug(u"onMessage buttonWillAppear: {}".format(message))
        buttonKey = safeKey(message[u'payload'][u'context'])
        button = {}
        button[u'id'] = message[u'payload'][u'context']
        button[u'device'] = message[u'payload'][u'device']
        button[u'column'] = message[u'payload'][u'payload'][u'coordinates'][
            u'column']
        button[u'row'] = message[u'payload'][u'payload'][u'coordinates'][
            u'row']
        button[u'settings'] = message[u'payload'][u'payload'][u'settings']
        button[u'name'] = "{}-{}-{}".format(
            self.known_devices[button[u'device']][u'name'], button[u'column'],
            button[u'row'])
        button[u'client'] = client['id']

        self.known_buttons[buttonKey] = button

        if buttonKey in self.activeButtons:  # If there's an active Indigo device, update the visible state
            buttonDevice = indigo.devices.get(
                int(self.activeButtons[buttonKey]))
            if not buttonDevice:
                self.logger.warning(
                    "buttonWillAppear invalid button DeviceID: {}".format(
                        message))
            else:
                states_list = []
                states_list.append({'key': 'visible', 'value': True})
                states_list.append({'key': 'name', 'value': button[u'name']})
                states_list.append({
                    'key': 'column',
                    'value': button[u'column']
                })
                states_list.append({'key': 'row', 'value': button[u'row']})
                states_list.append({'key': 'clientID', 'value': client['id']})
                buttonDevice.updateStatesOnServer(states_list)

    def buttonWillDisappear(self, message, client):
        self.logger.debug(u"onMessage buttonWillDisappear: {}".format(message))
        buttonKey = safeKey(message[u'payload'][u'context'])
        if buttonKey in self.activeButtons:  # If there's an active Indigo device, update the visible state
            buttonDevice = indigo.devices.get(
                int(self.activeButtons[buttonKey]))
            if not buttonDevice:
                self.logger.warning(
                    "buttonWillDisappear invalid button DeviceID: {}".format(
                        message))
            else:
                deckDevice.updateStateOnServer(key="visible", value=False)

    def keyPress(self, message, client):
        self.logger.debug(u"onMessage keyPress: {}".format(message))

        messageType = message['message-type']
        if messageType not in ['keyDown', 'keyUp']:
            self.logger.warning("keyPress unexpected message-type: {}".format(
                message['message-type']))
            return

        eventID = message[u'payload'][u'payload'][u'settings'][u'eventID']
        actionRequest = message[u'payload'][u'payload'][u'settings'][
            u'actionRequest']

        if actionRequest == u'action-group' and messageType == 'keyDown':

            indigo.actionGroup.execute(int(eventID))

        elif actionRequest == u'indigo-device-momentary' and messageType == 'keyDown':

            indigo.device.turnOn(int(eventID))

        elif actionRequest == u'indigo-device-momentary' and messageType == 'keyUp':

            indigo.device.turnOff(int(eventID))

        elif actionRequest == u'indigo-device-toggle' and messageType == 'keyDown':

            indigo.device.toggle(int(eventID))

        elif actionRequest == u'indigo-variable':

            indigo.variable.updateValue(int(eventID), value=messageType)

    ########################################

    def validateDeviceConfigUi(self, valuesDict, typeId, devId):
        errorsDict = indigo.Dict()
        if len(errorsDict) > 0:
            return (False, valuesDict, errorsDict)
        return (True, valuesDict)

    ########################################
    # Called for each enabled Device belonging to plugin
    ########################################

    def deviceStartComm(self, device):
        self.logger.info(u"{}: Starting Device".format(device.name))

        instanceVers = int(device.pluginProps.get('devVersCount', 0))
        if instanceVers == kCurDevVersCount:
            self.logger.threaddebug(
                u"{}: Device is current version: {}".format(
                    device.name, instanceVers))
        elif instanceVers < kCurDevVersCount:
            newProps = device.pluginProps
            newProps["devVersCount"] = kCurDevVersCount
            device.replacePluginPropsOnServer(newProps)
            self.logger.debug(u"{}: Updated device version: {} -> {}".format(
                device.name, instanceVers, kCurDevVersCount))
        else:
            self.logger.warning(u"{}: Invalid device version: {}".format(
                device.name, instanceVers))

        if device.deviceTypeId == "sdDevice":
            self.activeDecks[device.address] = device.id
        elif device.deviceTypeId == "sdButton":
            self.activeButtons[device.address] = device.id

    def deviceStopComm(self, device):
        self.logger.info(u"{}: Stopping Device".format(device.name))
        if device.deviceTypeId == "sdDevice":
            del self.activeDecks[device.id]
        elif device.deviceTypeId == "sdButton":
            del self.activeButtons[device.id]

    ########################################
    # Plugin Actions object callbacks
    ########################################

    def setButtonIcon(self, pluginAction, deckDevice, callerWaitingForResult):
        self.logger.debug(
            "setButtonIcon: pluginAction = {}".format(pluginAction))

        profile = indigo.activePlugin.substitute(pluginAction.props["profile"])
        deckDeviceID = pluginAction.props["device"]
        deckDict = self.known_devices[safeKey(deckDeviceID)]
        self.logger.debug("setButtonIcon: deckDict = {}".format(deckDict))
        socketClient = self.activeConnections[deckDict['client']]

        message = {
            "event": "setButtonIcon",
            "device": deckDeviceID,
            "profile": profile
        }
        self.wsServer.send_message(socketClient, json.dumps(message))

    def availableDeckList(self,
                          filter="",
                          valuesDict=None,
                          typeId="",
                          targetId=0):

        retList = []
        for key in self.known_devices:
            device = self.known_devices[key]
            retList.append((device['id'], device['name']))

        self.logger.debug("availableDeckList: retList = {}".format(retList))
        return retList

    def availableButtonList(self,
                            filter="",
                            valuesDict=None,
                            typeId="",
                            targetId=0):

        in_use = []
        for dev in indigo.devices.iter(filter="self.sdButton"):
            in_use.append(dev.pluginProps[u'context'])

        retList = []
        for key in self.known_buttons:
            button = self.known_buttons[key]
            if button['id'] not in in_use:
                retList.append((button['id'], button['name']))

        self.logger.debug("availableButtonList: retList = {}".format(retList))
        return retList

    # doesn't do anything, just needed to force other menus to dynamically refresh
    def menuChanged(self, valuesDict=None, typeId=None, devId=None):
        return valuesDict

    def validateActionConfigUi(self, valuesDict, typeId, devId):
        errorsDict = indigo.Dict()
        try:
            pass
        except:
            pass
        if len(errorsDict) > 0:
            return (False, valuesDict, errorsDict)
        return (True, valuesDict)

    ########################################
    # Menu Command Methods
    ########################################

    def dumpKnownDevices(self):
        self.logger.info(u"Known decks:\n{}".format(self.known_devices))
        self.logger.info(u"Known buttons:\n{}".format(self.known_buttons))

    def dumpActiveDevices(self):
        self.logger.info(u"Active decks:\n{}".format(self.activeDecks))
        self.logger.info(u"Active buttons:\n{}".format(self.activeButtons))

    def purgeKnownDevices(self):
        self.known_devices = {}
        self.known_buttons = {}

    ########################################
    # Event/Trigger Methods
    ########################################

    def triggerStartProcessing(self, trigger):
        self.logger.debug("Adding Trigger %s (%d) - %s" %
                          (trigger.name, trigger.id, trigger.pluginTypeId))
        assert trigger.id not in self.triggers
        self.triggers[trigger.id] = trigger

    def triggerStopProcessing(self, trigger):
        self.logger.debug("Removing Trigger %s (%d)" %
                          (trigger.name, trigger.id))
        assert trigger.id in self.triggers
        del self.triggers[trigger.id]

    def triggerCheck(self, device):

        for triggerId, trigger in sorted(self.triggers.iteritems()):
            self.logger.debug("Checking Trigger %s (%s), Type: %s" %
                              (trigger.name, trigger.id, trigger.pluginTypeId))