moved statemachine-related code to statemachine.py

This commit is contained in:
Frederic 2017-04-27 21:19:26 +02:00
parent 60c899edcc
commit c5d305295b
3 changed files with 345 additions and 367 deletions

View File

@ -1,9 +1,8 @@
import csv import csv
import ConfigParser import ConfigParser
import apparatinterface import apparatinterface
import phoneinterface import phoneinterface
import fetapd import statemachine
class ConfigurationReader(object): class ConfigurationReader(object):
DEFAULTS = { DEFAULTS = {
@ -82,7 +81,7 @@ class ConfigurationReader(object):
invert_gs = self.__get_global_val_bool('invert_gs'), 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.__get_global_val_int('dial_timeout'),
self.__read_shortcuts(), self.__read_shortcuts(),
self.__read_blacklist(), self.__read_blacklist(),

380
fetapd.py
View File

@ -1,370 +1,43 @@
import time import time
import threading
import Queue as queue
from phoneinterface import PhoneInterface, PhoneEvent from phoneinterface import PhoneInterface, PhoneEvent
from apparatinterface import FeApPinConfiguration, FeApUserInterface from apparatinterface import FeApPinConfiguration, FeApUserInterface
import configreader import configreader
import statemachine
class DialConfiguration(object): controller = None
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))
c = None
def gabelschalter_cb(state): def gabelschalter_cb(state):
global c
if state == 1: if state == 1:
c.queue_event('gabelschalter_up') controller.queue_event('gabelschalter_up')
else: else:
c.queue_event('gabelschalter_down') controller.queue_event('gabelschalter_down')
def nummernschalter_active_cb(): def nummernschalter_active_cb():
global c controller.queue_event('nummernschalter_active')
c.queue_event('nummernschalter_active')
def nummernschalter_done_cb(digit): def nummernschalter_done_cb(digit):
global c controller.queue_event('nummernschalter_input', digit)
c.queue_event('nummernschalter_input', digit)
def phone_cb(event): def phone_cb(event):
if event == PhoneEvent.RegInProgress: if event == PhoneEvent.RegInProgress:
c.queue_event('registration_in_progress') controller.queue_event('registration_in_progress')
elif event == PhoneEvent.RegSuccessfull: elif event == PhoneEvent.RegSuccessfull:
c.queue_event('registration_successful') controller.queue_event('registration_successful')
elif event == PhoneEvent.RegLost: elif event == PhoneEvent.RegLost:
c.queue_event('registration_lost') controller.queue_event('registration_lost')
elif event == PhoneEvent.CallIncoming: elif event == PhoneEvent.CallIncoming:
c.queue_event('incoming_call') controller.queue_event('incoming_call')
elif event == PhoneEvent.CallAccepted: elif event == PhoneEvent.CallAccepted:
c.queue_event('call_accepted') controller.queue_event('call_accepted')
elif event == PhoneEvent.CallEnded: elif event == PhoneEvent.CallEnded:
c.queue_event('call_ended') controller.queue_event('call_ended')
elif event == PhoneEvent.CallRinging: elif event == PhoneEvent.CallRinging:
c.queue_event('call_ringing') controller.queue_event('call_ringing')
elif event == PhoneEvent.CallBusy: elif event == PhoneEvent.CallBusy:
c.queue_event('call_ended') controller.queue_event('call_ended')
elif event == PhoneEvent.CallInvalidNumber: elif event == PhoneEvent.CallInvalidNumber:
c.queue_event('invalid_number') controller.queue_event('invalid_number')
if __name__ == '__main__': if __name__ == '__main__':
cfg = configreader.ConfigurationReader() cfg = configreader.ConfigurationReader()
@ -372,34 +45,13 @@ if __name__ == '__main__':
phone = PhoneInterface(cfg.phoneconfig) phone = PhoneInterface(cfg.phoneconfig)
feap = FeApUserInterface(cfg.pinconfig) feap = FeApUserInterface(cfg.pinconfig)
c = StateMachineController(phone, feap, cfg.dialconfig) controller = statemachine.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)
phone.start() phone.start()
try: try:
while True: while True:
time.sleep(1) 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: except KeyboardInterrupt:
phone.stop() phone.stop()
feap.set_wecker(False) feap.set_wecker(False)

327
statemachine.py Normal file
View File

@ -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))