<commit_before>''' Trigger manager, and trigger objects '''
from threading import Thread

#from flameEffects import FlameEffects
import json
import logging
import time
import socket
import sys

triggerList = list()
triggerThread = None

logger = logging.getLogger('triggers')

def init(triggerFile, addr, port):
    global triggerThread
    print "INIT"
    try:
        with open(triggerFile) as f:
            print "opened file", triggerFile
            triggerParams = json.load(f) 
        print "triggerparams are", triggerParams
        triggerThread = TriggerManager(addr, port, triggerParams)
        triggerThread.start()
    except:
        logger.exception("Exception initializing triggers!")
        
def shutdown():
    print "Stopping trigger thread"
    triggerThread.stop()
    triggerThread.join()
        
def _verifyTriggerParamsObject(triggerParamsObject):
    # XXX - I'm doing a lot of assignments here, but the point isn't to assign variables,
    # its to test that the structure of the object is correct. There are probably better
    # ways to do this, but for now...
    try:
        name = triggerParamsObject["name"]
        points = triggerParamsObject["points"]
        for point in points:
            type = point["type"]
            if type != "linger" and type != "passthrough":
                raise KeyError("linger or passthrough required")
            x = point["x"]
            y = point["y"]
            z = point["z"]
            transitTime = point["transitTime"]
            if type == "linger":
                lingerTime  = point["lingerTime"]
        # NB - there should be at least one associated flame effect at the end, but
        # I'm going to punt on checking for it. The code will not crash without it.    
        return True
    except KeyError:
        log.exception("Malformed trigger parameter")
        return False
       
class TriggerManager(Thread): 
    ''' Maintains list of trigger objects, and feeds them data from the hydraulics 
        position system '''
    def __init__(self, listenAddr, listenPort, triggerParams):
        Thread.__init__(self)
        self.triggerList = list()
        self.listenAddr = listenAddr
        self.listenPort = listenPort
        self.running    = False
        self.socketInit = False
        self.hydraulics_socket = None
        
        print triggerParams

        for triggerParamsObject in triggerParams:
            print triggerParamsObject
            if _verifyTriggerParamsObject(triggerParamsObject):
                print "appending trigger object"
                triggerObject = TriggerObject(triggerParamsObject["name"], 
                                              triggerParamsObject["points"])
                self.triggerList.append(triggerObject)
                triggerObject.enable(True)
                              
# x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) # XXX this is a way of creating an object with attributes instead of a generic dict, but I don't quite understand how it works yet
                
    def run(self):
        self.running = True
        while self.running:
            try:
                if not self.socketInit:
                    self._connectHydraulicsSocket(self.listenAddr, self.listenPort)
                    self.socketInit = True
                    logger.info("Connected to hydraulics socket")
                
                # listen for message on queue
                msg = self.hydraulics_socket.recv(32)
                if len(msg) <= 0:
                    logger.warn("0 bytes received on trigger thread, disconnecting")  
                    #self.hydraulics_socket.shutdown(socket.SHUT_RDWR)
                    self.hydraulics_socket.close()
                    self.socketInit = False
                    continue
                    
                print 'received message on position socket', msg
                    
                msgObj = json.loads(msg) # XXX and what happens on exception here?            
                
                # got message, hand to objects
                for trigger in self.triggerList:
                    print "handing message to trigger"
                    trigger.processSculpturePosition(msgObj)
            except socket.error, (value, message):
                logger.exception("Socket error {}".format(message))
                # Attempt reconnect
                self.socketInit = False
                if self.hydraulics_socket != None:
                    #self.hydraulics_socket.shutdown(socket.SHUT_RDWR)
                    self.hydraulics_socket.close()
                    self.hydraulics_socket = None
                time.sleep(3)
            except:
                logger.exception("Error processing hydraulics position data")
                
    def stop(self):
        print "In manager thread. set running false"
        self.running = False
                
    def _connectHydraulicsSocket(self, addr, port):
        try:
            newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            newSocket.connect((addr, port))
            self.hydraulics_socket = newSocket
        except socket.error as e:
            #logger.exception("Socket error {}".format(message))
            newSocket.close()
            raise e
        # NB - these calls are expected to throw if there is a problem


            

# states are
DISABLED = "disabled"
LINGERING = "lingering"
LOOKING = "looking"



ENVELOPE_SIZE = 5

# XXX let's not bother with direction down the envelope right now. Just keep us in the envelope
# XXX - want a way to disable these guys!

class TriggerObject():
    ''' Object associated with a particular trigger sequence. The trigger manager feeds it
        position data from the sculpture; it is responsible for maintaining an internal 
        state (based upon past position data) and deciding when to trigger a flame effect '''
    def __init__(self, name, points):
        ''' params object is a list of points, 
            [{name:<triggerName>, points:[{type:"linger"|"passthrough", x:<x>, y:<y>, z:<z>, transitTime: <int>, <lingerTime:<int>>, <flameEffect:<effectName>>}]}]'''
        self.name   = name
        self.points = points
        self.state = DISABLED 
        self.pointTarget = 0 # target is first point
        self.lastTriggerPointTime = 0
        self.lingerTime = 0  # wall time - linger until this time
        print "INIT trigger object!!"
        
    def enable(self, bEnable):
        if bEnable:
            self._restartTrigger()
        else:
            self.state = DISABLED
        
    def isEnabled(self):
        return self.state != DISABLED
        
    def _restartTrigger(self):
        self.pointTarget = 0<commit_msg>adding support for enabling/disabling and getting information about current trigger state<commit_after>''' Trigger manager, and trigger objects '''

''' Handles triggering of pre-defined flame effects based on sculpture position. 
The TriggerManager listens to a socket over which sculpture position data is streamed, and 
feeds that information to a list of trigger objects. The trigger objects then call the flame
effects code when the appropriate conditions are met.

Triggers are defined in a json-formatted trigger file read in during initialization. 
Each individual object within the file has the following format:
{ "name":<name>, "points":["type":<"passthrough"|"linger">, "x":<x-coord>, "y":<y-coord>
                           "z":<z-coord>, "transitTime":<max time allowed to get to point
                                                         from previous point>
                           "lingerTime":<optional, time to linger if point type is "linger">,
                           "flameEffect":<optional, flame effect name to trigger>]} '''
                           
from threading import Thread
#from flameEffects import FlameEffects
import json
import logging
import time
import socket
import sys

triggerList = list()
triggerThread = None

logger = logging.getLogger('triggers')


def init(triggerFile, addr, port):
    global triggerThread
    print "INIT"
    logger.warning("wtf??")
    try:
        with open(triggerFile) as f:
            print "opened file", triggerFile
            triggerParams = json.load(f) 
        print "triggerparams are", triggerParams
        triggerThread = TriggerManager(addr, port, triggerParams)
        triggerThread.start()
    except IOError:
        logger.exception("Exception initializing triggers!")
        
def shutdown():
    print "Stopping trigger thread"
    global triggerThread
    if triggerThread != None:
        triggerThread.stop()
        triggerThread.join()
    triggerThread = None
    
def getTriggers():
    triggers = triggerThread.getTriggers()
    newTriggers = list()
    for trigger in triggers:
        newTrigger = {"name": trigger.getName(), "enabled": trigger.isEnabled(), 
                      "active": trigger.isActive()}
        newTriggers.append(newTrigger)
    return newTriggers
    
def enableTrigger(triggerName):
    triggers = triggerThread.getTriggers()
    for trigger in triggers:
        if trigger.getName() == triggerName:
            trigger.enable(True)
            break

def disableTrigger(triggerName):
    triggers = triggerThread.getTriggers()
    for trigger in triggers:
        if trigger.getName() == triggerName:
            trigger.enable(False)
            break
        
def _verifyTriggerParamsObject(triggerParamsObject):
    # XXX - I'm doing a lot of assignments here, but the point isn't to assign variables,
    # it's to test that the structure of the object is correct. There are probably better
    # ways to do this, but for now...
    try:
        name = triggerParamsObject["name"]
        points = triggerParamsObject["points"]
        for point in points:
            type = point["type"]
            if type != "linger" and type != "passthrough":
                raise KeyError("linger or passthrough required")
            x = point["x"]
            y = point["y"]
            z = point["z"]
            transitTime = point["transitTime"]
            if type == "linger":
                lingerTime  = point["lingerTime"]
        # NB - there should be at least one associated flame effect at the end, but
        # I'm going to punt on checking for it. The code will not crash without it.    
        return True
    except KeyError:
        log.exception("Malformed trigger parameter")
        return False
       
class TriggerManager(Thread): 
    ''' Maintains list of trigger objects, and feeds them data from the hydraulics 
        position system '''
    def __init__(self, listenAddr, listenPort, triggerParams):
        Thread.__init__(self)
        self.triggerList = list()
        self.listenAddr = listenAddr
        self.listenPort = listenPort
        self.running    = False
        self.socketInit = False
        self.hydraulics_socket = None
        
        print triggerParams

        for triggerParamsObject in triggerParams:
            print triggerParamsObject
            if _verifyTriggerParamsObject(triggerParamsObject):
                print "appending trigger object"
                triggerObject = TriggerObject(triggerParamsObject["name"], 
                                              triggerParamsObject["points"])
                self.triggerList.append(triggerObject)
                triggerObject.enable(True)
                              
# x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) # XXX this is a way of creating an object with attributes instead of a generic dict, but I don't quite understand how it works yet
                
    def run(self):
        self.running = True
        while self.running:
            try:
                if not self.socketInit:
                    self._connectHydraulicsSocket(self.listenAddr, self.listenPort)
                    self.socketInit = True
                    logger.info("Connected to hydraulics socket")
                
                # listen for message on queue
                msg = self.hydraulics_socket.recv(32)
                if len(msg) <= 0:
                    logger.warn("0 bytes received on trigger thread, disconnecting")  
                    #self.hydraulics_socket.shutdown(socket.SHUT_RDWR)
                    self.hydraulics_socket.close()
                    self.socketInit = False
                    continue
                    
                print 'received message on position socket', msg
                    
                msgObj = json.loads(msg) # XXX and what happens on exception here?            
                
                # got message, hand to objects
                for trigger in self.triggerList:
                    print "handing message to trigger"
                    trigger.processSculpturePosition(msgObj)
            except socket.error, (value, message):
                logger.exception("Socket error {}".format(message))
                # Attempt reconnect
                self.socketInit = False
                if self.hydraulics_socket != None:
                    #self.hydraulics_socket.shutdown(socket.SHUT_RDWR)
                    self.hydraulics_socket.close()
                    self.hydraulics_socket = None
                time.sleep(3)
            except:
                logger.exception("Error processing hydraulics position data")
                
    def stop(self):
        print "In manager thread. set running false"
        self.running = False
                
    def _connectHydraulicsSocket(self, addr, port):
        try:
            newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            newSocket.connect((addr, port))
            self.hydraulics_socket = newSocket
        except socket.error as e:
            #logger.exception("Socket error {}".format(message))
            newSocket.close()
            raise e
        # NB - these calls are expected to throw if there is a problem


            

# states are
DISABLED = "disabled"
LINGERING = "lingering" # waiting a while at a particular point
LOOKING = "looking"     # looking for a particular point


ENVELOPE_SIZE = 5

# XXX let's not bother with direction down the envelope right now. Just keep us in the envelope
# XXX - want a way to disable these guys!

class TriggerObject():
    ''' Object associated with a particular trigger sequence. The trigger manager feeds it
        position data from the sculpture; it is responsible for maintaining an internal 
        state (based upon past position data) and deciding when to trigger a flame effect '''
    def __init__(self, name, points):
        ''' params object is a list of points, 
            [{name:<triggerName>, points:[{type:"linger"|"passthrough", x:<x>, y:<y>, z:<z>, transitTime: <int>, <lingerTime:<int>>, <flameEffect:<effectName>>}]}]'''
        self.name   = name
        self.points = points
        self.state = DISABLED 
        self.pointTarget = 0 # target is first point
        self.lastTriggerPointTime = 0
        self.lingerTime = 0  # wall time - linger until this time
        print "INIT trigger object!!"
        
    def enable(self, bEnable):
        if bEnable:
            self._restartTrigger()
        else:
            self.state = DISABLED
            
    def getName(self):
        return self.name
        
    def isEnabled(self):
        return self.state != DISABLED
        
    def isActive(self):
        return (self.state == LINGERING) or (self.state == LOOKING and self.pointTarget > 0)
        
    def _restartTrigger(self):
        self.pointTarget = 0
{'fraction_non_alphanumeric': 0.0792488860598345, 'fraction_numerical': 0.001336728198599618, 'mean_word_length': 1.9733156699470098, 'pattern_counts': {'":': 16, '<': 31, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6049343', 'n_tokens_mistral': 4001, 'n_tokens_neox': 3810, 'n_words': 1283}