class MainThread(threading.Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None): super(MainThread, self).__init__(group, target, name, args, kwargs, verbose) # load settings file self.cfg = get_settings() # this is used to stop the arming thread self.pill2kill = None # keep a buzzer object to stop it from every function. same for the receiver433 self.buzzer = None # prepare a queue to share data between this threads and other thread self.shared_queue_keyborad = Queue.Queue() self.shared_queue_rfid_reader = Queue.Queue() self.shared_queue_433_receiver = Queue.Queue() self.shared_queue_message_from_api = Queue.Queue() # create an object to manage the screen try: self.screen_manager = AdafruitScreenManager() except IOError: raise Exception("No connection with the screen") # create an object to manage the arduino self.arduino = ArduinoManager() try: self.arduino.ping() except IOError: self.screen_manager.set_arduino_connection_missing() sys.exit(0) # run the keypad thread self.keypad_thread = MatrixKeypadManager(self.shared_queue_keyborad) self.keypad_thread.start() # run the thread that handle RFID rfid = RFIDrc522Manager(self.shared_queue_rfid_reader) rfid.start() # keep 433 receiver thread object in mind self.receiver443 = None # create the flask rest api app = Flask(__name__) flask_api = FlaskAPI(app, self.shared_queue_message_from_api, self) flask_api.start() # Save a buffer where we put each typed number self.code_buffer = "" self.last_state = "disabled" # create the state machine self.fsm = Fysom({'initial': self._get_initial_state(), 'events': [ {'name': 'arm', 'src': 'disabled', 'dst': 'arming'}, {'name': 'enable', 'src': 'arming', 'dst': 'enabled'}, {'name': 'intrusion', 'src': 'enabled', 'dst': 'waiting_code'}, {'name': 'alarm', 'src': 'waiting_code', 'dst': 'alarming'}, {'name': 'disable', 'src': ['arming', 'enabled', 'waiting_code', 'alarming'], 'dst': 'disabled'}], 'callbacks': { 'ondisabled': self.ondisabled, 'onarming': self.onarming, 'onenabled': self.onenabled, 'onwaiting_code': self.onwaiting_code, 'onalarming': self.onalarming} }) def run(self): print "Run main thread" while True: if not self.shared_queue_keyborad.empty(): val = self.shared_queue_keyborad.get() print "Key received from keypad: ", val if val == "D": self.screen_manager.switch_light() elif val == "C": # cancel only if we were in arming status if self.fsm.current == "arming": self.fsm.disable() else: # we add a star to the screen self.screen_manager.add_star() # add the value to the buffer self.code_buffer += str(val) # if we have 4 number we can test the code if len(self.code_buffer) == 4: self._test_pin_code() self.code_buffer = "" if not self.shared_queue_rfid_reader.empty(): val = self.shared_queue_rfid_reader.get() print "Received UID from RFID ", val self._test_rfid_uid(val) if not self.shared_queue_433_receiver.empty(): val = self.shared_queue_433_receiver.get() print "Received UID from 433 receiver ", val self._test_433_uid(val) time.sleep(0.1) def ondisabled(self, e): """ Disable the alarm system :return: """ # stop buzzer if self.buzzer is not None: self.buzzer.stop() # stop arduino counter if it was counting self.arduino.cancel_delayed_siren() # stop siren if it was ringing self.arduino.stop_siren() # if the last status was waiting code if self.last_state == "waiting_code": # then we stop the counter thread self.pill2kill.set() if self.last_state == "arming": self.pill2kill.set() self.screen_manager.cancel_arming() time.sleep(2) # wait 2 seconde with the cancel message before showing disabled status # show disabled on screen self.screen_manager.set_disabled() # we keep the last state in memory self.last_state = "disabled" def onarming(self, e): """ Arming the alarm. Count 20 second. During this time the action can be cancelled by the user :return: """ def doit(stop_event, screen_manager): max_time = self.cfg["arming_time"] while not stop_event.is_set(): for x in range(max_time, 0, -5): if not stop_event.is_set(): screen_manager.lcd.message("%s.." % str(x)) stop_event.wait(5) # counter over, if the user has not cancel, we active the alarm if not stop_event.is_set(): self.fsm.enable() self.screen_manager.reset() self.screen_manager.lcd.message("Arming...\n") self.pill2kill = threading.Event() t = threading.Thread(target=doit, args=(self.pill2kill, self.screen_manager)) t.start() # we start the buzzer self.buzzer = BuzzerManager() self.buzzer.mode = 2 self.buzzer.start() # we keep the last state in memory self.last_state = "arming" def onenabled(self, e): # switch status self.screen_manager.set_enabled() # stop buzzing self.buzzer.stop() # start the 433 receiver thread self.receiver443 = Receiver433Manager(self.shared_queue_433_receiver) self.receiver443.start() # Stop the counter thread self.pill2kill.set() # we keep the last state in memory self.last_state = "enabled" def onwaiting_code(self, e): """ Send a notification to the arduino, this one will sounds the alarm if no code provided :return: """ def doit(stop_event): while not stop_event.is_set(): # wait 20 secondes stop_event.wait(self.cfg["wait_time_before_alarm"]) stop_event.set() self.buzzer.stop() if self.fsm.current == "waiting_code": self.fsm.alarm(location=e.location) # stop the receiver, we do not need it anymore, intrusion already detected self.receiver443.stop() # send notification to the arduino if we are not in notification only mode if not self.cfg["notification_only"]: self.arduino.delayed_siren() # show info on screen self.screen_manager.set_intrustion_detected(e.location) # wait 20 sec the code to disable the alarm self.pill2kill = threading.Event() t = threading.Thread(target=doit, args=(self.pill2kill,)) t.start() # we start the buzzer self.buzzer = BuzzerManager() self.buzzer.mode = 1 self.buzzer.start() # we keep the last state in memory self.last_state = "waiting_code" def onalarming(self, e): """ Switch alarm """ print "Alarm !!" if self.buzzer is not None: self.buzzer.stop() self.screen_manager.set_alarm() # we keep the last state in memory self.last_state = "alarming" try: # send a notification. e.location can be empty if the Rpi has restarted message = "Intrusion: %s" % e.location notify(message) except AttributeError: print "No location in memory" def _test_pin_code(self): """ The user has entered 4 number. We test if typed code is the right one If the code is right, we switch the status of the system If the code is wrong we show a notification on the screen :return: """ print "Valid code is %s" % self.cfg["pin_code"] if str(self.code_buffer) == str(self.cfg["pin_code"]): print "Code valid" self._switch_status() else: print "Code invalid" self.screen_manager.print_invalid_code(self.last_state) def _switch_status(self): """ If we were disabled, switch to enabled, else switch to disabled :return: """ if self.fsm.current == "disabled": # the system was disabled, arming it self.fsm.arm() else: # in all other case, we want to disable self.fsm.disable() def update_status(self, new_alarm_status, new_siren_status): # first, switch the alamr status if new_alarm_status == "enabled": self.fsm.enable() else: self.fsm.disable() if new_siren_status == "on": print "Start the siren" self.arduino.start_siren() else: print "Stop the siren" self.arduino.stop_siren() def _test_rfid_uid(self, uid): # one little bip to notify the user that we scanned the card self.buzzer = BuzzerManager() self.buzzer.mode = 3 self.buzzer.start() time.sleep(1) self.buzzer.stop() if uid in self.cfg['rfid']['valid_uid']: print "Valid UID" self._switch_status() else: print "Invalid UID" self.screen_manager.print_invalid_card(self.last_state) def _test_433_uid(self, uid): """ Test the received uid against settings uid. Should be always a valid code :param uid: received ID from the 433MHZ receiver sent by a sensor :return: """ for el in self.cfg['sensors']: if uid == el['id']: print "Valid sensor ID from %s" % el['location'] # something has been detected self.fsm.intrusion(location=el['location']) # not need to test other ID break else: print "Sensor ID not reconized" def _get_initial_state(self): """ Get the initial state of the system. can be "disabled" if the siren is off or "alarm" if the siren is on :return: """ siren_status = self.arduino.get_siren_status() if siren_status == "off": return "disabled" else: return "alarming"
class gemHandler(secsHandler): """Baseclass for creating Host/Equipment models. This layer contains GEM functionality. Inherit from this class and override required functions. :param address: IP address of remote host :type address: string :param port: TCP port of remote host :type port: integer :param active: Is the connection active (*True*) or passive (*False*) :type active: boolean :param session_id: session / device ID to use for connection :type session_id: integer :param name: Name of the underlying configuration :type name: string :param event_handler: object for event handling :type event_handler: :class:`secsgem.common.EventHandler` :param custom_connection_handler: object for connection handling (ie multi server) :type custom_connection_handler: :class:`secsgem.hsmsConnections.HsmsMultiPassiveServer` """ ceids = secsHandler.ceids """Dictionary of available collection events, CEID is the key :param name: Name of the data value :type name: string :param CEID: Collection event the data value is used for :type CEID: integer """ dvs = secsHandler.dvs """Dictionary of available data values, DVID is the key :param name: Name of the collection event :type name: string :param dv: Data values available for collection event :type dv: list of integers """ alarms = secsHandler.alarms """Dictionary of available alarms, ALID is the key :param alarmText: Description of the alarm :type alarmText: string :param ceidOn: Collection event for activated alarm :type ceidOn: integer :param ceidOff: Collection event for deactivated alarm :type ceidOff: integer """ rcmds = secsHandler.rcmds """Dictionary of available remote commands, command is the key :param params: description of the parameters :type params: list of dictionary :param CEID: Collection events the remote command uses :type CEID: list of integers """ def __init__(self, address, port, active, session_id, name, event_handler=None, custom_connection_handler=None): secsHandler.__init__(self, address, port, active, session_id, name, event_handler, custom_connection_handler) self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__) # not going to HOST_INITIATED_CONNECT because fysom doesn't support two states. but there is a transistion to get out of EQUIPMENT_INITIATED_CONNECT when the HOST_INITIATED_CONNECT happens self.communicationState = Fysom({ 'initial': 'DISABLED', # 1 'events': [ {'name': 'enable', 'src': 'DISABLED', 'dst': 'ENABLED'}, # 2 {'name': 'disable', 'src': ['ENABLED', 'NOT_COMMUNICATING', 'COMMUNICATING', 'EQUIPMENT_INITIATED_CONNECT', 'WAIT_DELAY', 'WAIT_CRA', "HOST_INITIATED_CONNECT", "WAIT_CR_FROM_HOST"], 'dst': 'DISABLED'}, # 3 {'name': 'select', 'src': 'NOT_COMMUNICATING', 'dst': 'EQUIPMENT_INITIATED_CONNECT'}, # 5 {'name': 'communicationreqfail', 'src': 'WAIT_CRA', 'dst': 'WAIT_DELAY'}, # 6 {'name': 'delayexpired', 'src': 'WAIT_DELAY', 'dst': 'WAIT_CRA'}, # 7 {'name': 'messagereceived', 'src': 'WAIT_DELAY', 'dst': 'WAIT_CRA'}, # 8 {'name': 's1f14received', 'src': 'WAIT_CRA', 'dst': 'COMMUNICATING'}, # 9 {'name': 'communicationfail', 'src': 'COMMUNICATING', 'dst': 'NOT_COMMUNICATING'}, # 14 {'name': 's1f13received', 'src': ['WAIT_CR_FROM_HOST', 'WAIT_DELAY', 'WAIT_CRA'], 'dst': 'COMMUNICATING'}, # 15 (WAIT_CR_FROM_HOST is running in background - AND state - so if s1f13 is received we go all communicating) ], 'callbacks': { 'onWAIT_CRA': self._onStateWaitCRA, 'onWAIT_DELAY': self._onStateWaitDelay, 'onleaveWAIT_CRA': self._onStateLeaveWaitCRA, 'onleaveWAIT_DELAY': self._onStateLeaveWaitDelay, 'onCOMMUNICATING': self._onStateCommunicating, # 'onselect': self.onStateSelect, }, 'autoforward': [ {'src': 'ENABLED', 'dst': 'NOT_COMMUNICATING'}, # 4 {'src': 'EQUIPMENT_INITIATED_CONNECT', 'dst': 'WAIT_CRA'}, # 5 {'src': 'HOST_INITIATED_CONNECT', 'dst': 'WAIT_CR_FROM_HOST'}, # 10 ] }) self.waitCRATimer = None self.commDelayTimer = None self.commDelayTimeout = 10 self.reportIDCounter = 1000 self.reportSubscriptions = {} self.registerCallback(1, 1, self.S1F1Handler) self.registerCallback(1, 13, self.S1F13Handler) self.registerCallback(6, 11, self.S6F11Handler) self.registerCallback(10, 1, self.S10F1Handler) def _serializeData(self): """Returns data for serialization :returns: data to serialize for this object :rtype: dict """ data = secsHandler._serializeData(self) data.update({'communicationState': self.communicationState.current, 'commDelayTimeout': self.commDelayTimeout, 'reportIDCounter': self.reportIDCounter, 'reportSubscriptions': self.reportSubscriptions}) return data def enable(self): """Enables the connection""" self.connection.enable() self.communicationState.enable() self.logger.info("Connection enabled") def disable(self): """Disables the connection""" self.connection.disable() self.communicationState.disable() self.logger.info("Connection disabled") def _onHsmsPacketReceived(self, packet): """Packet received from hsms layer :param packet: received data packet :type packet: :class:`secsgem.hsmsPackets.hsmsPacket` """ message = self.secsDecode(packet) if message is None: self.logger.info("< %s", packet) else: self.logger.info("< %s\n%s", packet, message) if self.communicationState.isstate('WAIT_CRA'): if packet.header.stream == 1 and packet.header.function == 13: if self.isHost: self.sendStreamFunction(self.streamFunction(1, 14)({"COMMACK": 1, "DATA": {}})) else: self.sendStreamFunction(self.streamFunction(1, 14)({"COMMACK": 1, "DATA": {"MDLN": "secsgem", "SOFTREV": "0.0.3"}})) self.communicationState.s1f13received() elif packet.header.stream == 1 and packet.header.function == 14: self.communicationState.s1f14received() elif self.communicationState.isstate('WAIT_DELAY'): pass elif self.communicationState.isstate('COMMUNICATING'): # check if callbacks available for this stream and function callback_index = "s" + str(packet.header.stream) + "f" + str(packet.header.function) if callback_index in self.callbacks: threading.Thread(target=self._runCallbacks, args=(callback_index, packet), name="secsgem_gemHandler_callback_{}".format(callback_index)).start() else: self._queuePacket(packet) def _onHsmsSelect(self): """Selected received from hsms layer""" self.communicationState.select() def _onWaitCRATimeout(self): """Linktest time timed out, so send linktest request""" self.communicationState.communicationreqfail() def _onWaitCommDelayTimeout(self): """Linktest time timed out, so send linktest request""" self.communicationState.delayexpired() def _onStateWaitCRA(self, data): """Connection state model changed to state WAIT_CRA :param data: event attributes :type data: object """ self.logger.debug("connectionState -> WAIT_CRA") self.waitCRATimer = threading.Timer(self.connection.T3, self._onWaitCRATimeout) self.waitCRATimer.start() if self.isHost: self.sendStreamFunction(self.streamFunction(1, 13)()) else: self.sendStreamFunction(self.streamFunction(1, 13)("secsgem", "0.0.3")) def _onStateWaitDelay(self, data): """Connection state model changed to state WAIT_DELAY :param data: event attributes :type data: object """ self.logger.debug("connectionState -> WAIT_DELAY") self.commDelayTimer = threading.Timer(self.commDelayTimeout, self._onWaitCommDelayTimeout) self.commDelayTimer.start() def _onStateLeaveWaitCRA(self, data): """Connection state model changed to state WAIT_CRA :param data: event attributes :type data: object """ if self.waitCRATimer is not None: self.waitCRATimer.cancel() def _onStateLeaveWaitDelay(self, data): """Connection state model changed to state WAIT_DELAY :param data: event attributes :type data: object """ if self.commDelayTimer is not None: self.commDelayTimer.cancel() def _onStateCommunicating(self, data): """Connection state model changed to state COMMUNICATING :param data: event attributes :type data: object """ self.logger.debug("connectionState -> COMMUNICATING") self.fireEvent("HandlerCommunicating", {'handler': self}, True) def on_connection_closed(self): """Connection was closed""" self.logger.info("Connection was closed") # call parent handlers secsHandler.on_connection_closed(self) # update communication state self.communicationState.communicationfail() def clearCollectionEvents(self): """Clear all collection events""" self.logger.info("Clearing collection events") # clear subscribed reports self.reportSubscriptions = {} # disable all ceids self.disableCEIDs() # delete all reports self.disableCEIDReports() def subscribeCollectionEvent(self, ceid, dvs, report_id=None): """Subscribe to a collection event :param ceid: ID of the collection event :type ceid: integer :param dvs: DV IDs to add for collection event :type dvs: list of integers :param report_id: optional - ID for report, autonumbering if None :type report_id: integer """ self.logger.info("Subscribing to collection event {0}".format(ceid)) if report_id is None: report_id = self.reportIDCounter self.reportIDCounter += 1 # note subscribed reports self.reportSubscriptions[report_id] = dvs # create report self.sendAndWaitForResponse(self.streamFunction(2, 33)({"DATAID": 0, "DATA": [{"RPTID": report_id, "VID": dvs}]})) # link event report to collection event self.sendAndWaitForResponse(self.streamFunction(2, 35)({"DATAID": 0, "DATA": [{"CEID": ceid, "RPTID": [report_id]}]})) # enable collection event self.sendAndWaitForResponse(self.streamFunction(2, 37)({"CEED": True, "CEID": [ceid]})) def sendRemoteCommand(self, rcmd, params): """Send a remote command :param rcmd: Name of command :type rcmd: string :param params: DV IDs to add for collection event :type params: list of strings """ self.logger.info("Send RCMD {0}".format(rcmd)) s2f41 = self.streamFunction(2, 41)() s2f41.RCMD = rcmd for param in params: s2f41.PARAMS.append({"CPNAME": param[0], "CPVAL": param[1]}) # send remote command return self.secsDecode(self.sendAndWaitForResponse(s2f41)) def sendProcessProgram(self, ppid, ppbody): """Send a process program :param ppid: Transferred process programs ID :type ppid: string :param ppbody: Content of process program :type ppbody: string """ # send remote command self.logger.info("Send process program {0}".format(ppid)) return self.secsDecode(self.sendAndWaitForResponse(self.streamFunction(7, 3)({"ppid": ppid, "ppbody": ppbody}))).ACKC7 def requestProcessProgram(self, ppid): """Request a process program :param ppid: Transferred process programs ID :type ppid: string """ self.logger.info("Request process program {0}".format(ppid)) # send remote command s7f6 = self.secsDecode(self.sendAndWaitForResponse(self.streamFunction(7, 5)(ppid))) return s7f6.PPID, s7f6.PPBODY def deleteProcessPrograms(self, ppids): """Delete a list of process program :param ppids: Process programs to delete :type ppids: list of strings """ self.logger.info("Delete process programs {0}".format(ppids)) # send remote command return self.secsDecode(self.sendAndWaitForResponse(self.streamFunction(7, 17)(ppids))).ACKC7 def getProcessProgramList(self): """Get process program list """ self.logger.info("Get process program list") # send remote command return self.secsDecode(self.sendAndWaitForResponse(self.streamFunction(7, 19)())).get() def S1F1Handler(self, handler, packet): """Callback handler for Stream 1, Function 1, Are You There .. seealso:: :func:`secsgem.hsmsConnections.hsmsConnection.registerCallback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsmsHandler.hsmsHandler :param packet: complete message received :type packet: :class:`secsgem.hsmsPackets.hsmsPacket` """ handler.sendResponse(self.streamFunction(1, 2)(), packet.header.system) def S1F13Handler(self, handler, packet): """Callback handler for Stream 1, Function 13, Establish Communication Request .. seealso:: :func:`secsgem.hsmsConnections.hsmsConnection.registerCallback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsmsHandler.hsmsHandler :param packet: complete message received :type packet: :class:`secsgem.hsmsPackets.hsmsPacket` """ handler.sendResponse(self.streamFunction(1, 14)({"COMMACK": 0}), packet.header.system) def S6F11Handler(self, handler, packet): """Callback handler for Stream 6, Function 11, Establish Communication Request .. seealso:: :func:`secsgem.hsmsConnections.hsmsConnection.registerCallback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsmsHandler.hsmsHandler :param packet: complete message received :type packet: :class:`secsgem.hsmsPackets.hsmsPacket` """ message = self.secsDecode(packet) for report in message.RPT: report_dvs = self.reportSubscriptions[report.RPTID] report_values = report.V.get() values = [] for i, s in enumerate(report_dvs): values.append({"dvid": s, "value": report_values[i], "name": self.getDVIDName(s)}) print values data = {"ceid": message.CEID, "rptid": report.RPTID, "values": values, "name": self.getCEIDName(message.CEID), "handler": self.connection, 'peer': self} self.fireEvent("CollectionEventReceived", data) handler.sendResponse(self.streamFunction(6, 12)(0), packet.header.system) def S10F1Handler(self, handler, packet): """Callback handler for Stream 10, Function 1, Terminal Request .. seealso:: :func:`secsgem.hsmsConnections.hsmsConnection.registerCallback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsmsHandler.hsmsHandler :param packet: complete message received :type packet: :class:`secsgem.hsmsPackets.hsmsPacket` """ s10f1 = self.secsDecode(packet) handler.sendResponse(self.streamFunction(10, 2)(0), packet.header.system) self.fireEvent("TerminalReceived", {"text": s10f1.TEXT, "terminal": s10f1.TID, "handler": self.connection, 'peer': self})