<?xml version="1.0" encoding="utf-8"?>
<luaLibraries version="1.30.5">
  <luaLibrary name="callforward">
    <code><![CDATA[
local valid = false
local active = config.get( pathPrefix .. "active" )
local function updateLed()
    if not valid then
        key:setLed( "orange" )
    elseif active == "true" then
        key:setLed( "green" )
    else
        key:setLed( "off" )
    end
end
local function checkValid()
    local value = config.get( pathPrefix .. "target" )
    valid = value and value:len() > 0
end
local function checkActive()
    active = config.get( pathPrefix .. "active" )
end
local function configListener( path )
    checkValid()
    checkActive()
    updateLed()
end
function onKeyUp()
    --[] check for validity []--
    if not valid then
        return
    end
    local value = config.get( pathPrefix .. "active" )
    if value == "false" then
        config.set( pathPrefix .. "active", "true" )
    elseif value == "true" then
        config.set( pathPrefix .. "active", "false" )
    end
end
config.register( pathPrefix, configListener )
checkValid()
updateLed()]]></code>
  </luaLibrary>
  <luaLibrary name="subscription_key">
    <code><![CDATA[
-- returns a function that needs to be called to start this subscription_key, it takes the following parameters in this order:
local identity = "" -- index of the identity to be used
local activeColor = "green" -- which color to constantly light LED in when subscription reports 'active'
local fctName = "myKeyFct" -- name of this function to use in logs, e.g.: DND or CallForwardWhenBusy
local isValid = function() -- reports if key has all it's params set to valid values. Set to nil to keep this default
    if identity == "" or identity == nil then
        return false
    end
    return true
end
local createUri = function(onNotOff, user) -- returns the sip-uri (or at least the user-name-part) to turn the feature
    return ""                              -- either on or off through silent call. user contains the username of the
end                                        -- associated identity
---------------------------------------------------------------------------------------------------------------
local userpath = "/identities/identity[1]/" -- will be overwritten in start()
local subscriptionIsTrying = false
local subscriptionIsWorking = false
local activatedOnServer = false
local inLimbo = false -- true when silent call to change setting is send until new notify is received
local currentLimboTimerId = 42 -- we can only start timers, never stop -> use ID to discard obsoleted timers when they fire
local lastWasOnCmd = false -- whether last silent-call made was to activate the feature or not (aka de-activate)
local user = ""
local domain = ""
local ongoingSubscription = nil
local function updateLed()
    if subscriptionIsWorking then
        if activatedOnServer then
            key:setLed( activeColor )
        else
            key:setLed( "off" )
        end
    elseif subscriptionIsTrying then
        key:setLed("orange", true) -- trying
    else
        key:setLed("orange", false) -- failed, nothing can be done atm (phone will auto-retry)
    end
end
local function handleNotify(subscr, data, headers)
    subscriptionIsWorking = true
    subscriptionIsTrying = false
    local x = xml.eval(data)
    if x ~= nil then
        debug.log("received subscr data from "..subscr:getUri() , "i")
        inLimbo = false
        local set_activatedOnServer = false
        for _,dlg in ipairs(x) do
            state = dlg:find("state")
            if state and state[1] == "trying" then
                set_activatedOnServer = true
                break
            end
        end
        activatedOnServer = set_activatedOnServer
    else
        debug.log("received empty notify from "..subscr:getUri() , "e")
    end
    updateLed()
end
local function unsubscribe()
    subscriptionIsWorking = false
    subscriptionIsTrying = false
    if (ongoingSubscription) then
        ongoingSubscription:unsubscribe()
        ongoingSubscription = nil
    end
end
local function subscriptionTerminated()
    subscriptionIsWorking = false
    subscriptionIsTrying = false
    updateLed()
end
local function onSubscrIsTrying()
    subscriptionIsWorking = false
    subscriptionIsTrying = true
    updateLed()
end
local function subscribe()
    unsubscribe()
    ongoingSubscription = sip.subscribe{uri=createUri(true, user), onNotify=handleNotify, line=identity,
                                        onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
end
local function resubscribe()
    subscriptionIsWorking = false
    subscriptionIsTrying = false
    if (ongoingSubscription) then
        ongoingSubscription:resubscribe()
    else
        subscribe()
    end
end
local function identityChanged(path)
    new_user = config.get(userpath .. "username")
    new_domain = sip.identities.getDomain(identity);
    if new_user ~= user or new_domain ~= domain then
        unsubscribe()
        user = new_user
        domain = new_domain
        subscribe()
        updateLed()
    end
end
local function setupRegChange()
    config.register(userpath, identityChanged)
    identityChanged()
end
local function newLimboTimer(myId)
    timer = function ()
        if inLimbo and myId == currentLimboTimerId then
            debug.log("no news from server after we've issued a change command, trying to force update through re-subscribing", "w")
            resubscribe()
            updateLed()
        end
    end
    return timer
end
local function sendCmd (turnItOnNotOff)
    lastWasOnCmd = turnItOnNotOff
    local url =  createUri(turnItOnNotOff, user)
    if url == "" or url == nil then
        debug.log("there is no way to "..(turnItOnNotOff and "activate " or "deactivate ")..fctName, "e")
    else
        debug.log("calling server to "..(turnItOnNotOff and "activate " or "deactivate ")..fctName, "d")
        if not inLimbo then
            inLimbo = true
            currentLimboTimerId = currentLimboTimerId + 1
            time.callbackIn(newLimboTimer(currentLimboTimerId), 14)
        end
        sip.invite{uri=url, line=identity, hidden=true}
        key:setLed(activeColor, true)
    end
end
function onKeyUp()
    debug.log(fctName.."-key pressed", "d")
    if not isValid() then
        debug.log("missing parameter(s), "..fctName.."-key won't work ever", "e")
    elseif not ongoingSubscription then
        debug.log("subscription had failed before, attempting restart", "i")
        subscribe()
        updateLed()
    elseif not subscriptionIsWorking then
        debug.log("subscription is being tried: stopping and restarting", "i")
        resubscribe()
        updateLed()
    else
        if inLimbo then
            debug.log("no news from server yet, trying the opposite command instead", "i")
            sendCmd(not lastWasOnCmd);
        else
            sendCmd(not activatedOnServer);
        end
    end
end
local startFkt = function(id, aColor, name, validFkt, cUri)
    identity = id
    userpath = "/identities/identity["..id.."]/"
    activeColor = aColor
    fctName = name
    createUri = cUri
    if validFkt then
      isValid = validFkt
    end
    if isValid() then
        setupRegChange()
    else
        debug.log("missing parameters, "..fctName.."-key won't work", "e")
    end
    updateLed()
end
return startFkt]]></code>
  </luaLibrary>
  <luaLibrary name="subscription_key_optional">
    <code><![CDATA[
-- key that tries to get a subscription from PBX for feature-code ##8*26 + [relay-id] + # + [user]. If that fails,
-- it enters a compatibility-mode for old pbx'es, were key just assumes/guesses about actual state in pbx
-- returns a function that needs to be called to start this key, it takes the following 4 parameters in this order:
local identity = "1"
local relid = "900"
local fctName = "opt subscr key" -- name of this to use in logs
-- the main difference between relay- and auto_config_switch-templates was, that on key-press the former always send a
-- toggle-cmd to pbx and blinked green for 2 secs while the later always either send a on- or off-cmd and lid the led
-- either green or off:
local isCustomRelay = true
----------------------------------------------------------------------------------------------------------------v
local dtmf_sequenz = "%23%238*26"
local activeBlinkFor2secs = false
local activeColor = "green" -- which color to constantly light LED in when subscription reports 'active'
local fctName = "auto_cfg_enable at "..tostring(key.MODULE)..":"..tostring(key.INDEX)  -- name of this to use in logs
local userpath = "/identities/identity["..identity.."]/"
local subscriptionIsTrying = false
local subscriptionIsWorking = false
local activatedOnServer = false
local inLimbo = false -- true when silent call to change setting is send until new notify is received
local currentLimboTimerId = 42 -- we can only start timers, never stop -> use ID to discard obsoleted timers when they fire
local lastWasOnCmd = false -- whether last silent-call made was to activate the feature or not (aka de-activate)
local user = ""
local domain = ""
local ongoingSubscription = nil
-- compatibility-mode with old pbx'es that don't know the subscription:
-- subscription will be stopped and never retried once it fails on first subscr-attempt.
-- State is reset when identity de-registers
local subscriptionWorkedOnce = false -- true when initial subsc-attempt worked
local withSubscription = true -- wether key currently uses subscription. Set to false when first subscr-attempt fails
local isValid = function() -- reports if key has all it's params set to valid values. Set to nil to keep this default
  if identity == "" or identity == nil or relid == "" or relid == nil then
    return false
  end
  return true
end
-- sip-uri to call to turn the feature on or off
local function createUri(onNotOff, user)
  if isCustomRelay then -- toggle
    return dtmf_sequenz..relid.."%23"      --"sip:"..##8*26900#
  elseif onNotOff then
    return dtmf_sequenz..relid.."*1%23"    --"sip:"..##8*26900*1#
  else
    return dtmf_sequenz..relid.."*0%23"    --"sip:"..##8*26900*0#
  end
end
-- the sip-uri to be used when subscribing for the state of this feature
local function createSubscrUri(user)
  return dtmf_sequenz..relid.."%23"..user  --"sip:"..##8*26900# + user
end
local function updateLed()
  if not withSubscription then
    if activeBlinkFor2secs then
      activeBlinkFor2secs = false
      key:setLed(activeColor, true)
      time.sleep("2")
      key:setLed("off")
    elseif activatedOnServer then
      key:setLed(activeColor)
    else
      key:setLed("off")
    end
  elseif subscriptionIsWorking then
    if activatedOnServer then
      key:setLed(activeColor)
    else
      key:setLed("off")
    end
  elseif subscriptionIsTrying then
    key:setLed("orange", true) -- trying
  else
    key:setLed("orange", false) -- failed, nothing can be done atm (phone will auto-retry)
  end
end
local function handleNotify(subscr, data, headers)
  subscriptionIsWorking = true
  subscriptionIsTrying = false
  local x = xml.eval(data)
  if x ~= nil then
    debug.log(fctName..": received subscr data from "..subscr:getUri() , "i")
    subscriptionWorkedOnce = false -- it worked once, remove marker, we know pbx supports this subscr
    inLimbo = false
    local set_activatedOnServer = false
    for _,dlg in ipairs(x) do
      state = dlg:find("state")
      if state and state[1] == "trying" then
        set_activatedOnServer = true
        break
      end
    end
    activatedOnServer = set_activatedOnServer
  else
    debug.log(fctName..": received empty notify from "..subscr:getUri() , "e")
  end
  updateLed()
end
local function unsubscribe()
  subscriptionIsWorking = false
  subscriptionIsTrying = false
  if (ongoingSubscription) then
    ongoingSubscription:unsubscribe()
    ongoingSubscription = nil
  end
end
local function subscriptionTerminated()
  subscriptionIsWorking = false
  subscriptionIsTrying = false
  if not subscriptionWorkedOnce then
    debug.log(fctName..": initial subscr-attempt failed, entering compatibility mode" , "w")
    withSubscription = false
  end
  updateLed()
end
local function onSubscrIsTrying()
  subscriptionIsWorking = false
  subscriptionIsTrying = true
  updateLed()
end
local function subscribe()
  unsubscribe()
  if withSubscription then
    ongoingSubscription = sip.subscribe{uri=createSubscrUri(user), onNotify=handleNotify, line=identity,
                                        onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
  else
    debug.log(fctName..": ignoring subscribe-request in compatibility mode", "w")
  end
end
local function resubscribe()
    subscriptionIsWorking = false
    subscriptionIsTrying = false
    if (ongoingSubscription) then
        ongoingSubscription:resubscribe()
    else
        subscribe()
    end
end
local function identityChanged(path)
    new_user = config.get(userpath .. "username")
    new_domain = sip.identities.getDomain(identity);
    if new_user ~= user or new_domain ~= domain then
        unsubscribe()
        user = new_user
        domain = new_domain
        subscribe()
        updateLed()
    end
end
function onIdRegChg(newState, idIdx)
    if newState == "unregistered" then
        -- reset subscr-once-compatibility-mode
        debug.log(fctName..": reseting compatibility mode un de-register", "i")
        withSubscription = not isCustomRelay
        subscriptionWorkedOnce = false
        subscribe()
        updateLed()
    end
end
local function setupRegChange()
    config.register(userpath, identityChanged)
    identityChanged()
    sip.identities.listen{ callback=onIdRegChg, line=identity }
end
local function newLimboTimer(myId)
    timer = function ()
        if inLimbo and myId == currentLimboTimerId then
            debug.log(fctName..": no news from server after we've issued a change command, trying to force update through re-subscribing", "w")
            resubscribe()
            updateLed()
        end
    end
    return timer
end
local function sendCmd (turnItOnNotOff)
  lastWasOnCmd = turnItOnNotOff
  local url =  createUri(turnItOnNotOff, user)
  if url == "" or url == nil then
    debug.log("there is no way to "..(turnItOnNotOff and "activate " or "deactivate ")..fctName, "e")
  else
    debug.log("calling server to "..(turnItOnNotOff and "activate " or "deactivate ")..fctName, "d")
    if withSubscription then
      if not inLimbo then
        inLimbo = true
        currentLimboTimerId = currentLimboTimerId + 1
        time.callbackIn(newLimboTimer(currentLimboTimerId), 14)
      end
      key:setLed(activeColor, true)
    else
      if isCustomRelay then
        url = dtmf_sequenz..relid.."%23"
        sip.invite{uri=url, line=identity, hidden=true}
        updateLed()
        return
      end
      activatedOnServer = turnItOnNotOff
      updateLed()
    end
    sip.invite{uri=url, line=identity, hidden=true}
  end
end
function onKeyUp()
  debug.log(fctName.."-key pressed", "d")
  if not isValid() then
    debug.log("missing parameter(s), "..fctName.."-key won't work ever", "e")
  elseif withSubscription then
    if not ongoingSubscription then
      debug.log(fctName..": subscription had failed before, attempting restart", "i")
      subscribe()
      updateLed()
    elseif not subscriptionIsWorking then
      debug.log(fctName..": subscription is being tried: stopping and restarting", "i")
      resubscribe()
      updateLed()
    else
      if inLimbo then
        debug.log(fctName..": no news from server yet, trying the opposite command instead", "i")
        sendCmd(not lastWasOnCmd);
      else
        sendCmd(not activatedOnServer);
      end
    end
  else
    if isCustomRelay then
      debug.log(fctName..": in compatibility mode -> just send on-cmd and let led blink green for 2 secs", "d")
      activeBlinkFor2secs = true
      sendCmd(true);
      updateLed();
    else
      debug.log(fctName..": in compatibility mode -> just send opposite of previous cmd which was active = "..tostring(activatedOnServer), "d")
      sendCmd(not activatedOnServer);
      updateLed();
    end
  end
end
local startFkt = function(id, relayId, name, isCustom)
  identity = id
  relid = relayId
  fctName = name
  isCustomRelay = isCustom
  withSubscription = not isCustomRelay
  userpath = "/identities/identity["..id.."]/"
  if isValid() then
    setupRegChange()
  else
    debug.log("missing parameters, "..fctName.."-key won't work", "e")
  end
  updateLed()
end
return startFkt]]></code>
  </luaLibrary>
</luaLibraries>