Nach einer längeren Testphase von 3cx, stellte sich heraus, dass 3CX bei allen Themen rund um Presence nur am eigenen Sofphone richtig funktioniert. Völlig uninteressant wenn man ein echtes VOIP-Telefon hat, dass die Funktionen ordnetlich abbilden könnte.
freepbx ist da out-of-the-box wesentlich besser zu steuern.
DND war relativ einfach umzusetzen, da die hints schon vorhanden waren. Ich habe aber einen anderen Status verwendet, um echtes Läuten des Telefons und DND zu unterscheiden.
nano /etc/asterisk/extensions_override_freepbx.conf
[app-dnd-on]
include => app-dnd-on-custom
exten => *78,1,Macro(user-callerid,)
exten => *78,n,Set(CONNECTEDLINE(name-charset,i)=utf8)
exten => *78,n,Set(CONNECTEDLINE(name,i)=Do Not Disturb: ON)
exten => *78,n,Set(CONNECTEDLINE(num,i)=${AMPUSER})
exten => *78,n,Answer
exten => *78,n,Wait(1)
exten => *78,n,Set(DB(DND/${AMPUSER})=YES)
exten => *78,n,Set(STATE=ONHOLD)
exten => *78,n,Gosub(app-dnd-on,sstate,1())
exten => *78,n(hook_1),Playback(do-not-disturb&activated)
exten => *78,n,Macro(hangupcall,)
Warum auch immer werden extesions von freepbx korrekt als unavailable erkannt, wenn die Identity sich abmeldet, aber der Status des hints hüpft nicht auf Unavailable.
Also schnell ein kleines python script gebaut, das alle 5 Sekunden die extensions kontrolliert und den hint aktualisiert wenn notwendig. Ginge vermutlich sauberer, wenn ein Script dauernd läuft und das event abfängt, aber egal.
"""
Example to get list of active channels
"""
import asterisk.manager
import sys
endpoints = {12,13,14,15,16,17,18}
manager = asterisk.manager.Manager()
check_update = [] #extensions to check for update
update = [] #extensions to update
try:
# connect to the manager
try:
manager.connect('localhost')
manager.login('logoff_user', 'PASSWORD')
# get a status report
response = manager.status()
#print(response)
response = manager.command('pjsip list endpoints')
#print(response.data)
data = response.data
for endpoint in endpoints:
start = str(response.data.encode('utf-8')).find(str(endpoint)+'/'+str(endpoint))
end = start + 70
test = data[start:end].find('Unavailable')
if test > -1:
#extension is Unavailable
check_update.append(endpoint)
###### check if hint is already set to unavailable, if not update
import pdb
pdb.set_trace()
response = manager.command('core show hints')
#print(response.data)
data = response.data
for endpoint in check_update:
start = str(response.data.encode('utf-8')).find(str(endpoint)+'@ext-local')
end = start + 60
test = data[start:end].find('Unavailable')
if test == -1:
#extension is Unavailable
update.append(endpoint)
for extension in update:
response = manager.command('devstate change Custom:DND'+str(extension)+' UNAVAILABLE')
#print(response.data)
manager.logoff()
except asterisk.manager.ManagerSocketException as e:
print "Error connecting to the manager: %s" % e.strerror
sys.exit(1)
except asterisk.manager.ManagerAuthException as e:
print "Error logging in to the manager: %s" % e.strerror
sys.exit(1)
except asterisk.manager.ManagerException as e:
print "Error: %s" % e.strerror
sys.exit(1)
finally:
# remember to clean up
manager.close()
weil das Script alle 5 Sekunden laufen soll, hilft mir cron nicht, das kann nur ganze Minuten. Hier habe ich aber was gefunden das wunderbar klappt: https://stackoverflow.com/questions/9619362/running-a-cron-every-30-seconds
und dann noch die handgeschnitzte XML Definition für die Tasten:
<general type="AsteriskPresenceField"/>
<initialization>
<state value='initial'/>
<variable name='subscr_ext' value='12'/>
<variable name='subscr_proxy' value='xxx.xxx.xxx.xxx'/>
<variable name='pickup_code' value='*8$(subscr_ext)'/>
<variable name='subscr_uri' value='sip:$(subscr_ext)@$(subscr_proxy)'/>
<identity value='1'/>
</initialization>
<subscription type="presence" to="<$(subscr_uri)>" for="$(subscr_uri)"/>
<NotifyParsingRules type="applies">
<level1 translates_to='OK'>/presence[@entity="$(subscr_uri):5060"]</level1> <!-- OKAY-->
</NotifyParsingRules>
<NotifyParsingRules type="state">
<level1 translates_to="ringing">/dialog-info/dialog/state[.="early"]</level1>
<level1-1 translates_to="CALLING">/dialog-info/dialog[@direction="initiator"]</level1-1>
<level2 translates_to="ringing">/presence/note[.="Ringing"]</level2><!--OKAY-->
<level2-1 translates_to="CALLING">/dialog-info/dialog[@direction="initiator"]</level2-1>
<level3 translates_to="IN_A_CALL">/presence/note[.="On the phone"]</level3><!-- OKAY-->
<level4 translates_to="NOT_IN_USE">/presence/note[.="Ready"]</level4><!-- OKAY-->
<level5 translates_to="OFFLINE">/presence/tuple/status/basic[.="closed"]</level5><!-- OKAY-->
<level6 translates_to="DND">/presence/note[.="On hold"]</level6><!-- OKAY-->
<level7 translates_to="free"/>
</NotifyParsingRules>
<NotifyParsingRules type="variable" id="call_id" state="ringing">
<level1 fetch_attribute="call-id">/dialog-info/dialog[@call-id]</level1>
</NotifyParsingRules>
<NotifyParsingRules type="variable" id="remote_tag" state="ringing">
<level1 fetch_attribute="remote-tag">/dialog-info/dialog[@remote-tag]</level1>
</NotifyParsingRules>
<NotifyParsingRules type="variable" id="local_tag" state="ringing">
<level1 fetch_attribute="local-tag">/dialog-info/dialog[@local-tag]</level1>
</NotifyParsingRules>
<NotifyParsingRules type="variable" id="remote_uri" state="ringing">
<level1 fetch_attribute="uri">/dialog-info/dialog/remote/target[@uri]</level1>
</NotifyParsingRules>
<NotifyParsingRules type="variable" id="remote_name" state="ringing">
<level1 fetch_attribute="display">/dialog-info/dialog/remote/identity[@display]</level1>
</NotifyParsingRules>
<action>
<invite target="$(remote_name)<$(remote_uri)>" when="on press" state="ringing" request_uri="$(remote_uri)" replaces="$(call_id);to-tag=$(remote_tag);from-tag=$(local_tag)"/>
<dial target="$(subscr_uri)" when="on press"/>
</action>