Beispiel #1
0
class CallAttendant(object):
    """The CallAttendant provides call logging and call screening services."""
    def __init__(self, config):
        """
        The constructor initializes and starts the Call Attendant.
            :param config: the application config dict
        """
        # The application-wide configuration
        self.config = config

        # Open the database
        db_path = None
        if self.config["TESTING"]:
            self.db = sqlite3.connect(":memory:")
        else:
            self.db = sqlite3.connect(
                os.path.join(self.config['ROOT_PATH'],
                             self.config['DATABASE']))

        # Create a synchronized queue for incoming callers from the modem
        self._caller_queue = queue.Queue()

        # Screening subsystem
        self.logger = CallLogger(self.db, self.config)
        self.screener = CallScreener(self.db, self.config)

        # Hardware subsystem
        # Create the modem with the callback functions that it invokes
        # when incoming calls are received.
        self.modem = Modem(self.config, self.phone_ringing, self.handle_caller)
        # Initialize the visual indicators (LEDs)
        self.approved_indicator = ApprovedIndicator()
        self.blocked_indicator = BlockedIndicator()
        self.ring_indicator = RingIndicator()

        # Start the User Interface subsystem (Flask)
        # Skip if we're running functional tests, because when testing
        # we use a memory database which can't be shared between threads.
        if not self.config["TESTING"]:
            print("Staring the Flask webapp")
            webapp.start(config)

    def handle_caller(self, caller):
        """
        A callback function used by the modem that places the given
        caller object into the synchronized queue for processing by the
        run method.
            :param caller: a dict object with caller ID information
        """
        if self.config["DEBUG"]:
            print("Adding to caller queue:")
            pprint(caller)
        self._caller_queue.put(caller)

    def phone_ringing(self, enabled):
        """
        A callback fucntion used by the modem to signal if the phone
        is ringing. It controls the phone ringing status indicator.
            :param enabled: If True, signals the phone is ringing
        """
        if enabled:
            self.ring_indicator.turn_on()
        else:
            self.ring_indicator.turn_off()

    def run(self):
        """
        Processes incoming callers by logging, screening, blocking
        and/or recording messages.
        """
        # Get relevant config settings
        root_path = self.config['ROOT_PATH']
        screening_mode = self.config['SCREENING_MODE']
        # Get configuration subsets
        block = self.config.get_namespace("BLOCK_")
        blocked = self.config.get_namespace("BLOCKED_")
        voice_mail = self.config.get_namespace("VOICE_MAIL_")
        # Build some common paths
        blocked_greeting_file = os.path.join(root_path,
                                             blocked['greeting_file'])
        general_greeting_file = os.path.join(root_path,
                                             voice_mail['greeting_file'])
        goodbye_file = os.path.join(root_path, voice_mail['goodbye_file'])
        invalid_response_file = os.path.join(
            root_path, voice_mail['invalid_response_file'])
        leave_message_file = os.path.join(root_path,
                                          voice_mail['leave_message_file'])
        voice_mail_menu_file = os.path.join(root_path, voice_mail['menu_file'])
        message_path = os.path.join(root_path, voice_mail["message_folder"])
        # Ensure the message path exists
        if not os.path.exists(message_path):
            os.makedirs(message_path)

        # Instruct the modem to start feeding calls into the caller queue
        self.modem.handle_calls()

        # Process incoming calls
        while 1:
            try:
                # Wait (blocking) for a caller
                caller = self._caller_queue.get()

                # Perform the call screening
                caller_permitted = False
                caller_blocked = False

                # Check the whitelist
                if "whitelist" in screening_mode:
                    print("Checking whitelist(s)")
                    if self.screener.is_whitelisted(caller):
                        permitted_caller = True
                        caller["ACTION"] = "Permitted"
                        self.approved_indicator.turn_on()

                # Now check the blacklist if not preempted by whitelist
                if not caller_permitted and "blacklist" in screening_mode:
                    print("Checking blacklist(s)")
                    if self.screener.is_blacklisted(caller):
                        caller_blocked = True
                        caller["ACTION"] = "Blocked"
                        self.blocked_indicator.turn_on()

                if not caller_permitted and not caller_blocked:
                    caller["ACTION"] = "Screened"

                # Log every call to the database
                call_no = self.logger.log_caller(caller)

                # Apply the configured actions to blocked callers
                if caller_blocked:

                    # Build the filename for a potential message
                    message_file = os.path.join(
                        message_path, "{}_{}_{}_{}.wav".format(
                            call_no, caller["NMBR"],
                            caller["NAME"].replace('_', '-'),
                            datetime.now().strftime("%m%d%y_%H%M")))
                    # Go "off-hook"
                    # - Acquires a lock on the modem
                    # - MUST be followed by hang_up()
                    if self.modem.pick_up():
                        try:
                            # Play greeting
                            if "greeting" in blocked["actions"]:
                                self.modem.play_audio(blocked_greeting_file)

                            # Record message
                            if "record_message" in blocked["actions"]:
                                self.modem.play_audio(leave_message_file)
                                self.modem.record_audio(message_file)

                            # Enter voice mail
                            elif "voice_mail" in blocked["actions"]:
                                tries = 0
                                while tries < 3:
                                    self.modem.play_audio(voice_mail_menu_file)
                                    digit = self.modem.wait_for_keypress(5)
                                    if digit == '1':
                                        # Leave a message
                                        self.modem.play_audio(
                                            leave_message_file)
                                        self.modem.record_audio(message_file)
                                        time.sleep(1)
                                        self.modem.play_audio(goodbye_file)
                                        break
                                    elif digit == '0':
                                        # End this call
                                        self.modem.play_audio(goodbye_file)
                                        break
                                    elif digit == '':
                                        # Timeout
                                        break
                                    else:
                                        # Try again
                                        self.modem.play_audio(
                                            invalid_response_file)
                                        tries += 1
                        finally:
                            # Go "on-hook"
                            self.modem.hang_up()

            except Exception as e:
                print(e)
                return 1
Beispiel #2
0
class CallAttendant(object):
    """The CallAttendant provides call logging and call screening services."""
    def __init__(self, config):
        """
        The constructor initializes and starts the Call Attendant.
            :param config:
                the application config dict
        """
        # The application-wide configuration
        self.config = config

        # Thread synchonization object
        self._stop_event = threading.Event()

        # Open the database
        if self.config["TESTING"]:
            self.db = sqlite3.connect(":memory:")
        else:
            self.db = sqlite3.connect(self.config['DB_FILE'])

        # Create a synchronized queue for incoming callers from the modem
        self._caller_queue = queue.Queue()

        #  Initialize the visual indicators (LEDs)
        self.approved_indicator = ApprovedIndicator(
            self.config.get("GPIO_LED_APPROVED_PIN"),
            self.config.get("GPIO_LED_APPROVED_BRIGHTNESS", 100))
        self.blocked_indicator = BlockedIndicator(
            self.config.get("GPIO_LED_BLOCKED_PIN"),
            self.config.get("GPIO_LED_BLOCKED_BRIGHTNESS", 100))

        # Screening subsystem
        self.logger = CallLogger(self.db, self.config)
        self.screener = CallScreener(self.db, self.config)

        #  Hardware subsystem
        #  Create (and open) the modem
        self.modem = Modem(self.config)

        # Messaging subsystem
        self.voice_mail = VoiceMail(self.db, self.config, self.modem)

        # Start the User Interface subsystem (Flask)
        # Skip if we're running functional tests, because when testing
        # we use a memory database which can't be shared between threads.
        if not self.config["TESTING"]:
            print("Starting the Flask webapp")
            webapp.start(self.config)

    def handle_caller(self, caller):
        """
        A callback function used by the modem that places the given
        caller object into the synchronized queue for processing by the
        run method.
            :param caller:
                a dict object with caller ID information
        """
        if self.config["DEBUG"]:
            print("Adding to caller queue:")
            pprint(caller)
        self._caller_queue.put(caller)

    def run(self):
        """
        Processes incoming callers by logging, screening, blocking
        and/or recording messages.
            :returns: exit code 1 on error otherwise 0
        """
        # Get relevant config settings
        screening_mode = self.config['SCREENING_MODE']
        blocked = self.config.get_namespace("BLOCKED_")
        screened = self.config.get_namespace("SCREENED_")
        permitted = self.config.get_namespace("PERMITTED_")
        blocked_greeting_file = blocked['greeting_file']
        screened_greeting_file = screened['greeting_file']
        permitted_greeting_file = permitted['greeting_file']

        # Instruct the modem to start feeding calls into the caller queue
        self.modem.start(self.handle_caller)

        # If testing, allow queue to be filled before processing for clean, readable logs
        if self.config["TESTING"]:
            time.sleep(1)

        # Process incoming calls
        exit_code = 0
        caller = {}
        print("Waiting for call...")
        while not self._stop_event.is_set():
            try:
                # Wait (blocking) for a caller
                try:
                    caller = self._caller_queue.get(True, 3.0)
                except queue.Empty:
                    continue

                # An incoming call has occurred, log it
                number = caller["NMBR"]
                print("Incoming call from {}".format(number))

                # Vars used in the call screening
                caller_permitted = False
                caller_screened = False
                caller_blocked = False
                action = ""
                reason = ""

                # Check the whitelist
                if "whitelist" in screening_mode:
                    print("> Checking whitelist(s)")
                    is_whitelisted, reason = self.screener.is_whitelisted(
                        caller)
                    if is_whitelisted:
                        caller_permitted = True
                        action = "Permitted"
                        self.approved_indicator.blink()

                # Now check the blacklist if not preempted by whitelist
                if not caller_permitted and "blacklist" in screening_mode:
                    print("> Checking blacklist(s)")
                    is_blacklisted, reason = self.screener.is_blacklisted(
                        caller)
                    if is_blacklisted:
                        caller_blocked = True
                        action = "Blocked"
                        self.blocked_indicator.blink()

                if not caller_permitted and not caller_blocked:
                    caller_screened = True
                    action = "Screened"

                # Log every call to the database (and console)
                call_no = self.logger.log_caller(caller, action, reason)
                print("--> {} {}: {}".format(number, action, reason))

                # Gather the data used to answer the call
                if caller_permitted:
                    actions = permitted["actions"]
                    greeting = permitted_greeting_file
                    rings_before_answer = permitted["rings_before_answer"]
                elif caller_screened:
                    actions = screened["actions"]
                    greeting = screened_greeting_file
                    rings_before_answer = screened["rings_before_answer"]
                elif caller_blocked:
                    actions = blocked["actions"]
                    greeting = blocked_greeting_file
                    rings_before_answer = blocked["rings_before_answer"]

                # Wait for the callee to answer the phone, if configured to do so
                ok_to_answer = True
                ring_count = 1  # Already had at least 1 ring to get here
                while ring_count < rings_before_answer:
                    # In North America, the standard ring cadence is "2-4", or two seconds
                    # of ringing followed by four seconds of silence (33% Duty Cycle).
                    if self.modem.ring_event.wait(10.0):
                        ring_count = ring_count + 1
                        print(" > > > Ring count: {}".format(ring_count))
                    else:
                        # wait timeout; assume ringing has stopped before the ring count
                        # was reached because either the callee answered or caller hung up.
                        ok_to_answer = False
                        print(
                            " > > > Ringing stopped: Caller hung up or callee answered"
                        )
                        break

                # Answer the call!
                if ok_to_answer and len(actions) > 0:
                    self.answer_call(actions, greeting, call_no, caller)

                print("Waiting for next call...")

            except KeyboardInterrupt:
                print("** User initiated shutdown")
                self._stop_event.set()
            except Exception as e:
                pprint(e)
                print("** Error running callattendant")
                self._stop_event.set()
                exit_code = 1
        return exit_code

    def shutdown(self):
        print("Shutting down...")
        print("-> Stopping modem")
        self.modem.stop()
        print("-> Stopping voice mail")
        self.voice_mail.stop()
        print("-> Releasing resources")
        self.approved_indicator.close()
        self.blocked_indicator.close()

    def answer_call(self, actions, greeting, call_no, caller):
        """
        Answer the call with the supplied actions, e.g, voice mail,
        record message, or simply pickup and hang up.
            :param actions:
                A tuple containing the actions to take for this call
            :param greeting:
                The wav file to play to the caller upon answering
            :param call_no:
                The unique call number identifying this call
            :param caller:
                The caller ID data
        """
        # Go "off-hook" - Acquires a lock on the modem - MUST follow with hang_up()
        if self.modem.pick_up():
            try:
                # Play greeting
                if "greeting" in actions:
                    print(">> Playing greeting...")
                    self.modem.play_audio(greeting)

                # Record message
                if "record_message" in actions:
                    print(">> Recording message...")
                    self.voice_mail.record_message(call_no, caller)

                # Enter voice mail menu
                elif "voice_mail" in actions:
                    print(">> Starting voice mail...")
                    self.voice_mail.voice_messaging_menu(call_no, caller)

            except RuntimeError as e:
                print("** Error handling a blocked caller: {}".format(e))

            finally:
                # Go "on-hook"
                self.modem.hang_up()
Beispiel #3
0
class CallAttendant(object):
    """The CallAttendant provides call logging and call screening services."""
    def handler_caller(self, caller):
        """Places the caller record in synchronized queue for processing"""
        self._caller_queue.put(caller)

    def phone_ringing(self, enabled):
        """Controls the phone ringing status indicator."""
        if enabled:
            self.ring_indicator.turn_on()
        else:
            self.ring_indicator.turn_off()

    def __init__(self):
        """The constructor initializes and starts the Call Attendant"""
        self.settings = {}
        self.settings[
            "db_name"] = "callattendant.db"  # SQLite3 DB to store incoming call log, whitelist and blacklist
        self.settings[
            "screening_mode"] = "whitelist_and_blacklist"  # no_screening, whitelist_only, whitelist_and_blacklist, blacklist_only
        self.settings["bad_cid_patterns"] = ""  # regex name patterns to ignore
        self.settings["ignore_private_numbers"] = False  # Ignore "P" CID names
        self.settings["ignore_unknown_numbers"] = True  # Ignore "O" CID names
        self.settings["block_calls"] = True

        self.db = sqlite3.connect(self.settings["db_name"])

        # The current/last caller id
        self._caller_queue = Queue()

        # Visual indicators (LEDs)
        self.approved_indicator = ApprovedIndicator()
        self.blocked_indicator = BlockedIndicator()
        self.ring_indicator = RingIndicator()

        # Telephony subsystems
        self.logger = CallLogger(self.db)
        self.screener = CallScreener(self.db)
        self.modem = Modem(self)
        self.modem.handle_calls()

        # User Interface subsystem
        webapp.start()

        # Run the app
        while 1:
            """Processes incoming callers with logging and screening."""

            # Wait (blocking) for a caller
            caller = self._caller_queue.get()

            # Perform the call screening
            mode = self.settings["screening_mode"]
            whitelisted = False
            blacklisted = False
            if mode in ["whitelist_only", "whitelist_and_blacklist"]:
                print "Checking whitelist(s)"
                if self.screener.is_whitelisted(caller):
                    whitelisted = True
                    caller["NOTE"] = "Whitelisted"
                    self.approved_indicator.turn_on()

            if not whitelisted and mode in [
                    "blacklist_only", "whitelist_and_blacklist"
            ]:
                print "Checking blacklist(s)"
                if self.screener.is_blacklisted(caller):
                    blacklisted = True
                    caller["NOTE"] = "Blacklisted"
                    self.blocked_indicator.turn_on()
                    if self.settings["block_calls"]:
                        #~ self.modem.play_audio("sample.wav")
                        #~ self.modem.hang_up()
                        self.modem.block_call()

            # Log every call to the database
            self.logger.log_caller(caller)