From c5d305295b78262759f5f310e43d25714da9e4c0 Mon Sep 17 00:00:00 2001 From: Frederic Date: Thu, 27 Apr 2017 21:19:26 +0200 Subject: [PATCH] moved statemachine-related code to statemachine.py --- configreader.py | 5 +- fetapd.py | 380 ++---------------------------------------------- statemachine.py | 327 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 367 deletions(-) create mode 100644 statemachine.py diff --git a/configreader.py b/configreader.py index b63ff30..ddee9e1 100644 --- a/configreader.py +++ b/configreader.py @@ -1,9 +1,8 @@ - import csv import ConfigParser import apparatinterface import phoneinterface -import fetapd +import statemachine class ConfigurationReader(object): DEFAULTS = { @@ -82,7 +81,7 @@ class ConfigurationReader(object): invert_gs = self.__get_global_val_bool('invert_gs'), ) - self.__dialconfig = fetapd.DialConfiguration( + self.__dialconfig = statemachine.DialConfiguration( self.__get_global_val_int('dial_timeout'), self.__read_shortcuts(), self.__read_blacklist(), diff --git a/fetapd.py b/fetapd.py index 5680c9d..b3da7c8 100644 --- a/fetapd.py +++ b/fetapd.py @@ -1,370 +1,43 @@ import time -import threading -import Queue as queue from phoneinterface import PhoneInterface, PhoneEvent from apparatinterface import FeApPinConfiguration, FeApUserInterface import configreader +import statemachine -class DialConfiguration(object): - def __init__(self, dial_timeout, shortcuts, blacklist): - self.dial_timeout = dial_timeout - self.shortcuts = shortcuts - self.blacklist = blacklist +controller = None -class IllegalEventError(Exception): - pass - - -""" -An abstract state, needed to define all possible events. - -""" -class AbstractState(object): - def on_registration_in_progress(self): - raise IllegalEventError() - - def on_registration_successful(self): - raise IllegalEventError() - - def on_registration_lost(self): - raise IllegalEventError() - - def on_gabelschalter_up(self): - raise IllegalEventError() - - def on_gabelschalter_down(self): - raise IllegalEventError() - - def on_incoming_call(self): - raise IllegalEventError() - - def on_call_ended(self): - raise IllegalEventError() - - def on_call_accepted(self): - raise IllegalEventError() - - def on_call_ringing(self): - raise IllegalEventError() - - def on_invalid_number(self): - raise IllegalEventError() - - def on_nummernschalter_active(self): - raise IllegalEventError() - - def on_nummernschalter_input(self, num): - raise IllegalEventError() - - def on_timeout(self): - raise IllegalEventError() - - def leave(self): - return None - - -""" -The basic state that every other state inherits from. It defines default -behaviour for some events (overriden if necessary). - -""" -class BaseState(AbstractState): - def __init__(self, controller): - self._controller = controller - - def on_registration_lost(self): - return InitState - - def on_gabelschalter_up(self): - return None - - def on_gabelschalter_down(self): - return None - - def on_incoming_call(self): - self._controller.phone.decline_call() - - def on_call_ended(self): - # When an incoming call is declined, a call_ended event occurs, which - # needs to be ignored, here. - return None - - def on_nummernschalter_active(self): - return None - - def on_nummernschalter_input(self, num): - return None - - -class InitState(BaseState): - def __init__(self, controller): - super(InitState, self).__init__(controller) - self._controller.feap.set_schauzeichen(True) - - def on_registration_in_progress(self): - print('registration in progress') - return RegisteringState - -class RegisteringState(BaseState): - def __init__(self, controller): - super(RegisteringState, self).__init__(controller) - - def on_registration_successful(self): - print('registration successful') - self._controller.feap.set_schauzeichen(False) - return IdleState - -class IdleState(BaseState): - def on_incoming_call(self): - print('incomfing call') - caller = self._controller.phone.get_remote_number() - print('From: %s' % caller) - if caller in self._controller.dialconfig.blacklist: - print('Caller on blacklist - declining') - self._controller.phone.decline_call() - return CallTerminatingState - else: - return SchelltState - - def on_gabelschalter_up(self): - print('gabel up') - return DialingState - -class SchelltState(BaseState): - def __init__(self, controller): - super(SchelltState, self).__init__(controller) - self._controller.feap.set_wecker(True) - - def leave(self): - self._controller.feap.set_wecker(False) - - def on_gabelschalter_up(self): - return AcceptingState - - def on_call_ended(self): - return IdleState - -class AcceptingState(BaseState): - def __init__(self, controller): - super(AcceptingState, self).__init__(controller) - self._controller.phone.accept_call() - - def on_call_accepted(self): - return CallRunningState - -class CallTerminatingState(BaseState): - def __init__(self, controller): - super(CallTerminatingState, self).__init__(controller) - self._controller.phone.end_call() - - def on_call_ended(self): - return IdleState - - def on_call_accepted(self): - return None - -class ForgottenState(BaseState): - def on_gabelschalter_down(self): - return IdleState - -class BusyBeepingState(BaseState): - def __init__(self, controller): - super(BusyBeepingState, self).__init__(controller) - self._controller.phone.play_busy_tone() - - def leave(self): - self._controller.phone.stop_playing() - - def on_timeout(self): - return ForgottenState - - def on_gabelschalter_down(self): - return IdleState - -class CallRunningState(BaseState): - def on_gabelschalter_down(self): - return CallTerminatingState - - def on_call_ended(self): - return BusyBeepingState - -class WecktState(BaseState): - def __init__(self, controller): - super(WecktState, self).__init__(controller) - self._controller.phone.play_ringback_tone() - - def leave(self): - self._controller.phone.stop_playing() - - def on_gabelschalter_down(self): - return CallTerminatingState - - def on_call_ended(self): - return BusyBeepingState - - def on_call_accepted(self): - return CallRunningState - -class ConnectingState(BaseState): - def on_gabelschalter_down(self): - return CallTerminatingState - - def on_call_ringing(self): - return WecktState - - def on_call_accepted(self): - return CallRunningState - - def on_invalid_number(self): - # TODO: play sound - return BusyBeepingState - - def on_call_ended(self): - return BusyBeepingState - -class DialingState(BaseState): - def __init__(self, controller): - super(DialingState, self).__init__(controller) - self._controller.phone.play_dial_tone() - self.__dial_tone = True - self.__number = '' - - def leave(self): - if self.__dial_tone: - self._controller.phone.stop_playing() - self._controller.abort_timeout() - - def on_gabelschalter_down(self): - return IdleState - - def on_nummernschalter_active(self): - self._controller.abort_timeout() - if self.__dial_tone: - self._controller.phone.stop_playing() - - def on_nummernschalter_input(self, num): - print('nummernschalter: %d' % (num)) - if self.__dial_tone: - self._controller.phone.stop_playing() - self.__number += str(num) - self._controller.abort_timeout() - self._controller.set_timeout(self._controller.dialconfig.dial_timeout * 1000) - self._controller.phone.read_text(str(num)) - - def on_timeout(self): - number = self.__number - print 'Dialing number:', number - if number in self._controller.dialconfig.shortcuts: - number = self._controller.dialconfig.shortcuts[number] - print 'shortcut resolved:', number - self._controller.phone.call(number) - return ConnectingState - - -class StateMachineController(object): - def __init__(self, phone, feap, dialconfig): - self.__phone = phone - self.__feap = feap - self.__dialconfig = dialconfig - - self.__state = InitState(self) - - self.__timeout = None - - self.__running = True - self.__evqueue = queue.Queue() - self.__evthread = threading.Thread(target=self.__event_dispatcher) - self.__evthread.start() - - def __event_dispatcher(self): - while self.__running: - (evname, evargs, evkwargs) = self.__evqueue.get() - if not evname: - return - - print('!!! event: %s' % (evname)) - handler = getattr(self.__state, 'on_%s' % (evname)) - try: - newstate = handler(*evargs, **evkwargs) - except IllegalEventError: - print('illegal event occured!!!!!!!!!!!!!!!!!!!!', self.__state.__class__.__name__) - if not newstate: - continue - - self.__state.leave() - self.abort_timeout() - - oldstate = self.__state.__class__ - print('%s -> %s' % (oldstate.__name__, newstate.__name__)) - self.__state = newstate(self) - - def queue_event(self, evname, *evargs, **evkwargs): - if not hasattr(AbstractState, 'on_%s' % (evname)): - raise ValueError('Illegal event name: %s' % (evname)) - self.__evqueue.put((evname, evargs, evkwargs)) - - def set_timeout(self, timeout): - self.__timeout = threading.Timer(timeout/1000, self.queue_event, args=['timeout']) - self.__timeout.start() - - def abort_timeout(self): - if self.__timeout: - self.__timeout.cancel() - self.__timeout = None - - @property - def phone(self): - return self.__phone - - @property - def feap(self): - return self.__feap - - @property - def dialconfig(self): - return self.__dialconfig - - def stop(self, hard=False): - if hard: - self.__running = False - self.__evqueue.put((None, None, None)) - -c = None def gabelschalter_cb(state): - global c if state == 1: - c.queue_event('gabelschalter_up') + controller.queue_event('gabelschalter_up') else: - c.queue_event('gabelschalter_down') + controller.queue_event('gabelschalter_down') def nummernschalter_active_cb(): - global c - c.queue_event('nummernschalter_active') + controller.queue_event('nummernschalter_active') def nummernschalter_done_cb(digit): - global c - c.queue_event('nummernschalter_input', digit) + controller.queue_event('nummernschalter_input', digit) def phone_cb(event): if event == PhoneEvent.RegInProgress: - c.queue_event('registration_in_progress') + controller.queue_event('registration_in_progress') elif event == PhoneEvent.RegSuccessfull: - c.queue_event('registration_successful') + controller.queue_event('registration_successful') elif event == PhoneEvent.RegLost: - c.queue_event('registration_lost') + controller.queue_event('registration_lost') elif event == PhoneEvent.CallIncoming: - c.queue_event('incoming_call') + controller.queue_event('incoming_call') elif event == PhoneEvent.CallAccepted: - c.queue_event('call_accepted') + controller.queue_event('call_accepted') elif event == PhoneEvent.CallEnded: - c.queue_event('call_ended') + controller.queue_event('call_ended') elif event == PhoneEvent.CallRinging: - c.queue_event('call_ringing') + controller.queue_event('call_ringing') elif event == PhoneEvent.CallBusy: - c.queue_event('call_ended') + controller.queue_event('call_ended') elif event == PhoneEvent.CallInvalidNumber: - c.queue_event('invalid_number') + controller.queue_event('invalid_number') if __name__ == '__main__': cfg = configreader.ConfigurationReader() @@ -372,34 +45,13 @@ if __name__ == '__main__': phone = PhoneInterface(cfg.phoneconfig) feap = FeApUserInterface(cfg.pinconfig) - c = StateMachineController(phone, feap, cfg.dialconfig) - - feap.add_gabelschalter_callback(gabelschalter_cb) - feap.add_nummernschalter_active_callback(nummernschalter_active_cb) - feap.add_nummernschalter_done_callback(nummernschalter_done_cb) - phone.add_event_cb(phone_cb) + controller = statemachine.StateMachineController(phone, feap, cfg.dialconfig) phone.start() - try: while True: time.sleep(1) - ''' - c.queue_event('gabelschalter_up') - c.queue_event('nummernschalter_input', 4) - c.queue_event('nummernschalter_input', 2) - #c.queue_event('gabelschalter_down') - #c.queue_event('call_accepted') - c.queue_event('timeout') - c.queue_event('call_ringing') - #c.queue_event('gabelschalter_down') - c.queue_event('call_accepted') - c.queue_event('call_ended') - c.queue_event('timeout') - c.queue_event('gabelschalter_down') - ''' - except KeyboardInterrupt: phone.stop() feap.set_wecker(False) diff --git a/statemachine.py b/statemachine.py new file mode 100644 index 0000000..0b1373a --- /dev/null +++ b/statemachine.py @@ -0,0 +1,327 @@ +import threading +import Queue as queue + +class DialConfiguration(object): + def __init__(self, dial_timeout, shortcuts, blacklist): + self.dial_timeout = dial_timeout + self.shortcuts = shortcuts + self.blacklist = blacklist + +class IllegalEventError(Exception): + pass + + +""" +An abstract state, needed to define all possible events. + +""" +class AbstractState(object): + def on_registration_in_progress(self): + raise IllegalEventError() + + def on_registration_successful(self): + raise IllegalEventError() + + def on_registration_lost(self): + raise IllegalEventError() + + def on_gabelschalter_up(self): + raise IllegalEventError() + + def on_gabelschalter_down(self): + raise IllegalEventError() + + def on_incoming_call(self): + raise IllegalEventError() + + def on_call_ended(self): + raise IllegalEventError() + + def on_call_accepted(self): + raise IllegalEventError() + + def on_call_ringing(self): + raise IllegalEventError() + + def on_invalid_number(self): + raise IllegalEventError() + + def on_nummernschalter_active(self): + raise IllegalEventError() + + def on_nummernschalter_input(self, num): + raise IllegalEventError() + + def on_timeout(self): + raise IllegalEventError() + + def leave(self): + return None + + +""" +The basic state that every other state inherits from. It defines default +behaviour for some events (overriden if necessary). + +""" +class BaseState(AbstractState): + def __init__(self, controller): + self._controller = controller + + def on_registration_lost(self): + return InitState + + def on_gabelschalter_up(self): + return None + + def on_gabelschalter_down(self): + return None + + def on_incoming_call(self): + self._controller.phone.decline_call() + + def on_call_ended(self): + # When an incoming call is declined, a call_ended event occurs, which + # needs to be ignored, here. + return None + + def on_nummernschalter_active(self): + return None + + def on_nummernschalter_input(self, num): + return None + + +class InitState(BaseState): + def __init__(self, controller): + super(InitState, self).__init__(controller) + self._controller.feap.set_schauzeichen(True) + + def on_registration_in_progress(self): + print('registration in progress') + return RegisteringState + +class RegisteringState(BaseState): + def __init__(self, controller): + super(RegisteringState, self).__init__(controller) + + def on_registration_successful(self): + print('registration successful') + self._controller.feap.set_schauzeichen(False) + return IdleState + +class IdleState(BaseState): + def on_incoming_call(self): + print('incomfing call') + caller = self._controller.phone.get_remote_number() + print('From: %s' % caller) + if caller in self._controller.dialconfig.blacklist: + print('Caller on blacklist - declining') + self._controller.phone.decline_call() + return CallTerminatingState + else: + return SchelltState + + def on_gabelschalter_up(self): + print('gabel up') + return DialingState + +class SchelltState(BaseState): + def __init__(self, controller): + super(SchelltState, self).__init__(controller) + self._controller.feap.set_wecker(True) + + def leave(self): + self._controller.feap.set_wecker(False) + + def on_gabelschalter_up(self): + return AcceptingState + + def on_call_ended(self): + return IdleState + +class AcceptingState(BaseState): + def __init__(self, controller): + super(AcceptingState, self).__init__(controller) + self._controller.phone.accept_call() + + def on_call_accepted(self): + return CallRunningState + +class CallTerminatingState(BaseState): + def __init__(self, controller): + super(CallTerminatingState, self).__init__(controller) + self._controller.phone.end_call() + + def on_call_ended(self): + return IdleState + + def on_call_accepted(self): + return None + +class ForgottenState(BaseState): + def on_gabelschalter_down(self): + return IdleState + +class BusyBeepingState(BaseState): + def __init__(self, controller): + super(BusyBeepingState, self).__init__(controller) + self._controller.phone.play_busy_tone() + + def leave(self): + self._controller.phone.stop_playing() + + def on_timeout(self): + return ForgottenState + + def on_gabelschalter_down(self): + return IdleState + +class CallRunningState(BaseState): + def on_gabelschalter_down(self): + return CallTerminatingState + + def on_call_ended(self): + return BusyBeepingState + +class WecktState(BaseState): + def __init__(self, controller): + super(WecktState, self).__init__(controller) + self._controller.phone.play_ringback_tone() + + def leave(self): + self._controller.phone.stop_playing() + + def on_gabelschalter_down(self): + return CallTerminatingState + + def on_call_ended(self): + return BusyBeepingState + + def on_call_accepted(self): + return CallRunningState + +class ConnectingState(BaseState): + def on_gabelschalter_down(self): + return CallTerminatingState + + def on_call_ringing(self): + return WecktState + + def on_call_accepted(self): + return CallRunningState + + def on_invalid_number(self): + # TODO: play sound + return BusyBeepingState + + def on_call_ended(self): + return BusyBeepingState + +class DialingState(BaseState): + def __init__(self, controller): + super(DialingState, self).__init__(controller) + self._controller.phone.play_dial_tone() + self.__dial_tone = True + self.__number = '' + + def leave(self): + if self.__dial_tone: + self._controller.phone.stop_playing() + self._controller.abort_timeout() + + def on_gabelschalter_down(self): + return IdleState + + def on_nummernschalter_active(self): + self._controller.abort_timeout() + if self.__dial_tone: + self._controller.phone.stop_playing() + + def on_nummernschalter_input(self, num): + print('nummernschalter: %d' % (num)) + if self.__dial_tone: + self._controller.phone.stop_playing() + self.__number += str(num) + self._controller.abort_timeout() + self._controller.set_timeout(self._controller.dialconfig.dial_timeout * 1000) + self._controller.phone.read_text(str(num)) + + def on_timeout(self): + number = self.__number + print 'Dialing number:', number + if number in self._controller.dialconfig.shortcuts: + number = self._controller.dialconfig.shortcuts[number] + print 'shortcut resolved:', number + self._controller.phone.call(number) + return ConnectingState + + +class StateMachineController(object): + def __init__(self, phone, feap, dialconfig): + self.__phone = phone + self.__feap = feap + self.__dialconfig = dialconfig + + self.__state = InitState(self) + + self.__timeout = None + + self.__running = True + self.__evqueue = queue.Queue() + self.__evthread = threading.Thread(target=self.__event_dispatcher) + self.__evthread.start() + + def __event_dispatcher(self): + while self.__running: + (evname, evargs, evkwargs) = self.__evqueue.get() + if not evname: + return + + print('!!! event: %s' % (evname)) + handler = getattr(self.__state, 'on_%s' % (evname)) + try: + newstate = handler(*evargs, **evkwargs) + except IllegalEventError: + print('illegal event occured!!!!!!!!!!!!!!!!!!!!', self.__state.__class__.__name__) + if not newstate: + continue + + self.__state.leave() + self.abort_timeout() + + oldstate = self.__state.__class__ + print('%s -> %s' % (oldstate.__name__, newstate.__name__)) + self.__state = newstate(self) + + def queue_event(self, evname, *evargs, **evkwargs): + if not hasattr(AbstractState, 'on_%s' % (evname)): + raise ValueError('Illegal event name: %s' % (evname)) + self.__evqueue.put((evname, evargs, evkwargs)) + + def set_timeout(self, timeout): + self.__timeout = threading.Timer(timeout/1000, self.queue_event, args=['timeout']) + self.__timeout.start() + + def abort_timeout(self): + if self.__timeout: + self.__timeout.cancel() + self.__timeout = None + + @property + def phone(self): + return self.__phone + + @property + def feap(self): + return self.__feap + + @property + def dialconfig(self): + return self.__dialconfig + + def stop(self, hard=False): + if hard: + self.__running = False + self.__evqueue.put((None, None, None)) +