en:products:comfortel-d-series:developer:keys:templates:examples:teams_integration:speeddial_lua.xml

speeddial_lua.xml
  1. <templates>
  2. <template name="@string/speed_dial_title" prio="30" icon="@drawable/invite">
  3. <keyConfiguration>
  4. <lua>
  5. <code><![CDATA[local gUserpath = "/identities/identity["..pIdentity.."]/"
  6. local gSubscriptionIsTrying = false
  7. local gSubscriptionIsWorking = false
  8. local gUser = "" -- username of associated identity
  9. local gDomain = "" -- domain (registrar) of associated identity
  10. local gPickupCode = nil -- pickup-code of associated identity
  11. local gDoPickupWithReplaces = false -- whether or not to use a replaces header (instead of pickup-code) to pick a call
  12. local gRemoteUri = nil -- sipUri-object of remote
  13. local gRemoteUser = nil -- user-part of remote uri
  14. local gOngoingSubscription = nil
  15. local gWithSubscription = pSubscriptionEnabled=="true"
  16. local gWithPickup = pDoPickup=="true"
  17. local gWithMissed = pBlinkOnMissed=="true"
  18. local gWithAutoAnswer = pRequestAutoAnswer=="true"
  19. local gHaveMissedCall = false
  20. -- remember what we're signaling to the user, this will influence what is expected when key is pressed
  21. -- one of:
  22. -- - errors-state: error, trying
  23. -- - subscription-event: idle, pickable, busy
  24. -- - local call: calling, active, holding, ringing
  25. -- - missed call: missed
  26. -- - nothing going on: idle
  27. local gLastReportedState = nil
  28. local gLocalCallId = nil -- wenn we have local call with gRemoteUser -> store its call-id here (e.g. TC@4)
  29. local gLocalCallState = nil -- wenn we have local call with gRemoteUser -> store its state here (calling, active, holding or ringing)
  30. local gFctName = "Blf" -- name for this key in logs (uri and identity will be added during initialization)
  31. -- keep infos of all monitored calls on our remote, only used when gWithSubscription==true
  32. -- contains these entries:
  33. -- - "entity" : attribute from dialog-info-xml
  34. -- - "version" : attribute from dialog-info-xml
  35. -- - "metaState" : one of free/pickable/busy -> combined meta-state accumulated from all dialogs (monitored calls)
  36. -- - "activeDialogs" : array of all dialog-ids of the all dialogs responsible for signaled meta-state.
  37. -- - "dialogs" : map by dialog-id of all dialogs (monitored calls)
  38. local gDialogInfo = nil
  39.  
  40. local function isValid() -- reports if key has all it's params set to valid values. Set to nil to keep this default
  41. if pIdentity == "" or pIdentity == nil then
  42. return false
  43. end
  44. if pRemote == "" or pRemote == nil then
  45. return false
  46. end
  47. return true
  48. end
  49.  
  50. -- Create value for replaces-header for first pickable dialog
  51. local function getFirstReplacesInfo()
  52. if #gDialogInfo.activeDialogs == 0 or not gDialogInfo.metaState == "pickable" then
  53. debug.log(gFctName..": cannot find pickable call for replaces", "e")
  54. return nil
  55. end
  56. local dlgXml = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]
  57. local callId = dlgXml["call-id"]
  58. if not callId or string.len(callId) == 0 then
  59. debug.log(gFctName..": cannot find call-id for replaces-header", "e")
  60. return nil
  61. end
  62. return sipTools.mkReplaces{callId=callId, to=dlgXml["remote-tag"], from=dlgXml["local-tag"]}
  63. end
  64.  
  65. -- analyze existing dialogs and find a text that describes who our monitored remote is talking to
  66. local function getBlfRemoteText()
  67. if #gDialogInfo.activeDialogs == 0 then
  68. return nil
  69. elseif #gDialogInfo.activeDialogs > 1 and not gDoPickupWithReplaces then
  70. -- wouldn't know which one would be picked
  71. return "multiple calls"
  72. end
  73. -- find something to name the pick-party:
  74. local remote = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]:find("remote")
  75. if not remote then
  76. debug.log(gFctName..": missing <remote> within <dialog-info>, cannot name remote party", "w")
  77. return "@string/call_unknown"
  78. end
  79. local dispName = remote:find("identity", "display")
  80. if dispName and string.len(dispName.display) > 0 then return dispName.display end
  81. local remote_usr = remote:find("identity")
  82. if not remote_usr then remote_usr = remote:find("target") end
  83. if not remote_usr or not remote_usr[1] then
  84. debug.log(gFctName..": missing remote-uri within <dialog-info>, cannot name remote party", "w")
  85. return "@string/call_unknown"
  86. end
  87. if string.find(string.lower(remote_usr[1]), "anonymous") then
  88. return "@string/call_anonyme"
  89. end
  90. local remote_uri = sipTools.parseUri{sipUri=remote_usr[1]}
  91. if not remote_uri or not remote_uri:getUiNumber() then
  92. return remote_usr[1]
  93. end
  94. return remote_uri:getUiNumber()
  95. end
  96.  
  97. local function updateKey()
  98. local desiredInfo = nil
  99. local desiredIcon = nil
  100.  
  101. if not isValid() then
  102. gLastReportedState = "error"
  103. key:setLed("orange", false)
  104. desiredInfo = "setup failed"
  105. desiredIcon = "@drawable/block"
  106. else
  107. gLastReportedState = "idle"
  108. if gLocalCallState == "calling" then
  109. gLastReportedState = "calling"
  110. key:setLed("red", true)
  111. desiredInfo = "calling"
  112. elseif gLocalCallState == "ringing" then
  113. gLastReportedState = "ringing"
  114. key:setLed("red", true)
  115. desiredInfo = "ringing"
  116. elseif gLocalCallState == "holding" then
  117. gLastReportedState = "holding"
  118. key:setLed("red", true)
  119. desiredInfo = "holding"
  120. elseif gLocalCallState == "active" then
  121. gLastReportedState = "active"
  122. key:setLed("red", false)
  123. desiredInfo = "active"
  124. elseif gWithSubscription then
  125. if gSubscriptionIsWorking then
  126. if gDialogInfo.metaState == "busy" then
  127. gLastReportedState = "busy"
  128. key:setLed("red", false)
  129. desiredInfo = getBlfRemoteText()
  130. elseif gDialogInfo.metaState == "pickable" then
  131. gLastReportedState = "pickable"
  132. key:setLed("red", true)
  133. desiredIcon = "@drawable/pick_up"
  134. desiredInfo = getBlfRemoteText()
  135. else
  136. gLastReportedState = "idle"
  137. key:setLed("off")
  138. end
  139. elseif gSubscriptionIsTrying then
  140. gLastReportedState = "trying"
  141. key:setLed("orange", true)
  142. desiredInfo = "trying to subscribe"
  143. else
  144. gLastReportedState = "error"
  145. key:setLed("orange", false)
  146. desiredInfo = "failed to subscribe"
  147. end
  148. else
  149. key:setLed("off")
  150. end
  151. if gLastReportedState == "idle" and gHaveMissedCall then
  152. gLastReportedState = "missed"
  153. key:setLed("green", true)
  154. desiredInfo = "missed call"
  155. end
  156. end
  157. key:setInfo(desiredInfo)
  158. key:setIcon(desiredIcon)
  159. end
  160.  
  161. function onDbResult(entries)
  162. gHaveMissedCall = false
  163. for i, queryParam in ipairs(entries) do
  164. local entryUser = sipTools.parseUri{sipUri=queryParam.number}:getUser()
  165. if entryUser == gRemoteUser then
  166. debug.log(gFctName..": found missed call from "..tostring(queryParam.number), "d")
  167. gHaveMissedCall = true
  168. break
  169. end
  170. end
  171. updateKey()
  172. end
  173.  
  174. function onCallEvent(event, call1, call2)
  175. if event == nil or call1 == nil then
  176. debug.log(gFctName..": invalid parameters at callListChanged("..tostring(event).." ,"..tostring(call1)..")", "e")
  177. return
  178. end
  179. local callId = call1:getId()
  180. local state = call1:getState()
  181. local number, name = call1:getRemote()
  182. local regId = tostring(call1:getIdentityIndex())
  183. if regId ~= pIdentity then
  184. return
  185. end
  186. debug.log("received "..tostring(event).." for call to "..tostring(number)..", id="..tostring(regId)..", state="..tostring(state)..", callId="..tostring(callId), "v")
  187. local remoteUri = sipTools.parseUri{sipUri=number}
  188. local remoteUser = remoteUri:getUser()
  189. if remoteUser ~= gRemoteUser then
  190. if callId == gLocalCallId then
  191. debug.log("call-partner of local "..callId.." changed, call no longer belongs to "..gFctName, "d")
  192. gLocalCallId = nil
  193. gLocalCallState = nil
  194. updateKey()
  195. end
  196. return
  197. end
  198. if state == "calling" or state == "ringing" or state == "active" or state == "holding" then
  199. if gLocalCallId ~= callId then
  200. debug.log(gFctName.." found new local call "..callId.." in state "..state, "d")
  201. elseif gLocalCallState ~= state then
  202. debug.log(gFctName.." "..callId.." changed state to "..state, "d")
  203. end
  204. gLocalCallId = callId
  205. gLocalCallState = state
  206. else
  207. debug.log(gFctName.." "..callId.." ended", "d")
  208. gLocalCallId = nil
  209. gLocalCallState = nil
  210. end
  211. updateKey()
  212. end
  213.  
  214. local function createDialogInfoStruct(dlgVers, dlgEntity)
  215. local result = {}
  216. result.entity = dlgEntity
  217. result.version = dlgVers
  218. result.metaState = "free"
  219. result.activeDialogs = {}
  220. result.dialogs = {}
  221. return result
  222. end
  223.  
  224. local function handleNotify(subscr, data, headers)
  225. gSubscriptionIsWorking = true
  226. gSubscriptionIsTrying = false
  227. local dlgInfo = xml.eval(data)
  228. if dlgInfo ~= nil then
  229. debug.log(gFctName.." received Notify", "d")
  230. if dlgInfo:tag() ~= "dialog-info" then
  231. debug.log(gFctName..": did not receive a dialog-info, ignoring "..tostring(dlgInfo:tag()) , "e")
  232. return
  233. end
  234. local dlgVers = dlgInfo.version
  235. local dlgEntity = dlgInfo.entity
  236. local dlgState = dlgInfo.state
  237. if not dlgEntity or not dlgVers or not dlgState then
  238. debug.log(gFctName..": received dialog-info is missing essencial attributes, ignoring dialog-info with "
  239. ..tostring(dlgVers).." / "..tostring(dlgEntity).." / "..tostring(dlgState) , "e")
  240. return
  241. end
  242. if not gDialogInfo then
  243. -- assuming this is the first dialog-info we've received -> initialize gDialogInfo
  244. gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
  245. else
  246. if gDialogInfo.entity ~= dlgEntity then -- strange -> begin again
  247. debug.log(gFctName..": entity in dialog-info changed, re-initializing gDialogInfo", "w")
  248. gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
  249. elseif gDialogInfo.version >= dlgVers then
  250. debug.log(gFctName..": received outdated dialog-info -> ignoring version "..tostring(dlgVers), "i")
  251. end
  252. end
  253. if dlgState == "full" then
  254. gDialogInfo.dialogs = {}
  255. end
  256. for _,dlg in ipairs(dlgInfo) do
  257. local dlgId = dlg.id
  258. if dlgId and string.len(dlgId) > 0 then
  259. gDialogInfo.dialogs[dlgId] = dlg
  260. end
  261. end
  262. -- evaluate metaState:
  263. local busyDlgs = {}
  264. local pickableDlgs = {}
  265. for dlgId, dlg in pairs(gDialogInfo.dialogs) do
  266. local stateXml = dlg:find("state")
  267. local state = nil
  268. if stateXml and #stateXml >= 1 then state = stateXml[1] end
  269. if state == "confirmed" then
  270. busyDlgs[#busyDlgs+1] = dlgId
  271. elseif state == "terminated" then
  272. -- ignore
  273. else
  274. -- assuming proceeding, early oder trying
  275. local direction = dlg.direction
  276. if direction == "initiator" then
  277. busyDlgs[#busyDlgs+1] = dlgId
  278. else
  279. pickableDlgs[#pickableDlgs+1] = dlgId
  280. end
  281. end
  282. end
  283. if #pickableDlgs > 0 then
  284. gDialogInfo.activeDialogs = pickableDlgs
  285. gDialogInfo.metaState = "pickable"
  286. elseif #busyDlgs > 0 then
  287. gDialogInfo.activeDialogs = busyDlgs
  288. gDialogInfo.metaState = "busy"
  289. else
  290. gDialogInfo.activeDialogs = {}
  291. gDialogInfo.metaState = "free"
  292. end
  293. debug.log(gFctName..": state from Notify: "..gDialogInfo.metaState, "d")
  294. else
  295. debug.log(gFctName..": received empty notify from "..subscr:getUri() , "e")
  296. end
  297. updateKey()
  298. end
  299.  
  300. local function unsubscribe()
  301. gSubscriptionIsWorking = false
  302. gSubscriptionIsTrying = false
  303. if (gOngoingSubscription) then
  304. gOngoingSubscription:unsubscribe()
  305. gOngoingSubscription = nil
  306. end
  307. end
  308.  
  309. local function subscriptionTerminated()
  310. gSubscriptionIsWorking = false
  311. gSubscriptionIsTrying = false
  312. updateKey()
  313. end
  314.  
  315. local function onSubscrIsTrying()
  316. gSubscriptionIsWorking = false
  317. gSubscriptionIsTrying = true
  318. updateKey()
  319. end
  320.  
  321. local function subscribe()
  322. unsubscribe()
  323. gOngoingSubscription = sip.subscribe{uri=pRemote, onNotify=handleNotify, line=pIdentity,
  324. onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
  325. end
  326.  
  327. local function identityChanged(path)
  328. gPickupCode = config.get(gUserpath .. "pickupCode")
  329. gDoPickupWithReplaces = not gPickupCode or string.len(gPickupCode) == 0
  330. local new_user = config.get(gUserpath .. "username")
  331. local new_domain = sip.identities.getDomain(pIdentity);
  332. if new_user ~= gUser or new_domain ~= domain then
  333. unsubscribe()
  334. gUser = new_user
  335. gDomain = new_domain
  336. subscribe()
  337. updateKey()
  338. end
  339. end
  340.  
  341. local function setupRegChange()
  342. config.register(gUserpath, identityChanged)
  343. identityChanged()
  344. end
  345.  
  346. function onKeyUp()
  347. local headers = {}
  348. local doPickup = false
  349.  
  350. debug.log(gFctName.."-key pressed", "d")
  351. if not isValid() then
  352. debug.log("missing parameter(s), "..gFctName.."-key won't work ever", "e")
  353. return
  354. elseif gLastReportedState == "error" then
  355. debug.log(gFctName..": subscription had failed before, attempting restart", "d")
  356. subscribe()
  357. updateKey()
  358. return
  359. elseif gLastReportedState == "trying" then
  360. debug.log(gFctName..": subscription is being tried: stopping and restarting", "d")
  361. unsubscribe()
  362. subscribe()
  363. updateKey()
  364. return
  365. elseif gLastReportedState == "calling" then
  366. debug.log(gFctName..": outgoing call thats not connected yet -> ignore key-press", "d")
  367. return
  368. elseif gLastReportedState == "active" then
  369. debug.log(gFctName..": connected call -> hold", "d")
  370. sip.calls.hold(gLocalCallId)
  371. return
  372. elseif gLastReportedState == "holding" then
  373. debug.log(gFctName..": held call -> retrieve", "d")
  374. sip.calls.resume(gLocalCallId)
  375. return
  376. elseif gLastReportedState == "ringing" then
  377. debug.log(gFctName..": ringing call -> accept", "d")
  378. sip.calls.accept(gLocalCallId)
  379. return
  380. elseif gLastReportedState == "pickable" then
  381. debug.log(gFctName..": picking the call", "d")
  382. local replHdr, replVal = getFirstReplacesInfo()
  383. if replHdr then -- always add replace, even with pickup-code .. cant hurt
  384. headers[replHdr] = replVal
  385. end
  386. if not gDoPickupWithReplaces then
  387. debug.log(gFctName..": picking with pickup-code '"..gPickupCode.."'", "d")
  388. doPickup = true
  389. else
  390. if replHdr then
  391. debug.log(gFctName..": picking with Replaces-Header: "..replVal, "d")
  392. else
  393. debug.log(gFctName..": cannot pick, replaces-info unavailable", "e")
  394. return
  395. end
  396. end
  397. else
  398. debug.log(gFctName..": simply calling the remote (state: "..gLastReportedState..", autoAnswer: "..tostring(gWithAutoAnswer)..")", "d")
  399. if gWithAutoAnswer then
  400. headers['Alert-Info'] = '<http://192.168.182.156>;info=alert-autoanswer'
  401. end
  402. end
  403. sip.invite{uri=pRemote, line=pIdentity, hidden=false, headers=headers, pickup=doPickup}
  404. end
  405.  
  406. function initialize()
  407. if isValid() then
  408. setupRegChange()
  409. else
  410. debug.log("missing parameters, "..gFctName.."-key won't work", "e")
  411. updateKey()
  412. return
  413. end
  414. gRemoteUri = sipTools.parseUri{sipUri=pRemote, fromUserInput=true, idIdx=pIdentity}
  415. gRemoteUser = gRemoteUri:getUser()
  416. local dbQuery = "(is_read=0 OR is_read is NULL ) AND new=1 AND subscription_id="..pIdentity.." AND number LIKE '%"..gRemoteUser.."%' AND (type=3 OR type=5)"
  417. database.subscribe{dbUri="content://call_log/calls", onChanged=onDbResult, query=dbQuery, columns={"number"}}
  418. sip.calls.listen(onCallEvent)
  419. gFctName = "BLF["..gRemoteUri:getUiNumber().." on id "..pIdentity.."]"
  420. updateKey()
  421. end
  422.  
  423. initialize()]]></code>
  424. <params>
  425. <param name="pRemote"/>
  426. <param name="pIdentity"/>
  427. <param name="pSubscriptionEnabled"><value>true</value></param>
  428. <param name="pDoPickup"><value>true</value></param>
  429. <param name="pRequestAutoAnswer"><value>false</value></param>
  430. <param name="pBlinkOnMissed"><value>true</value></param>
  431. </params>
  432. </lua>
  433. </keyConfiguration>
  434. <parameters>
  435. <parameter name="@string/uri" type="text">
  436. <path>//param[@name="pRemote"]/value</path>
  437. </parameter>
  438. <parameter optional="true" name="@string/identity" type="identity">
  439. <path>//param[@name="pIdentity"]/value</path>
  440. </parameter>
  441. <parameter optional="true" name="@string/subscription_enabled" type="boolean">
  442. <path>//param[@name="pSubscriptionEnabled"]/value</path>
  443. </parameter>
  444. <parameter optional="true" name="@string/do_pickup" type="boolean">
  445. <path>//param[@name="pDoPickup"]/value</path>
  446. </parameter>
  447. <parameter optional="true" name="@string/auto_answer" type="boolean">
  448. <path>//param[@name="pRequestAutoAnswer"]/value</path>
  449. </parameter>
  450. <parameter optional="true" name="@string/with_calllog" type="boolean">
  451. <path>//param[@name="pBlinkOnMissed"]/value</path>
  452. </parameter>
  453. </parameters>
  454. </template>
  455. </templates>
  • en/products/comfortel-d-series/developer/keys/templates/examples/teams_integration/speeddial_lua.xml.txt
  • Last modified: 22.03.2022 13:18
  • by hoehne