fetapi/statemachine.py

335 lines
8.7 KiB
Python

import threading
import 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
class AbstractState(object):
"""
An abstract state, needed to define all possible events.
"""
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
class BaseState(AbstractState):
"""
The basic state that every other state inherits from. It defines default
behaviour for some events (overriden if necessary).
"""
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)
self.publish_state()
def publish_state(self):
pass
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
def stop(self, hard=False):
if hard:
self.__running = False
self.__evqueue.put((None, None, None))