Add and delete voiceProfiles via provision script

Hi there,
I want to automatically add or delete user voiceprofiles with a provision script.

I don’t want to delete all and then add everything like in the example of " Creating/deleting object instances" in the docs like:

// Ensure that *all* other instances are deleted
declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[]", null, {path: 0});

// Add the two entries we care about
declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[SourceIPAddress:192.168.1.0,SourceNetMask:255.255.255.0]",  {path: now}, {path: 1});
declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[SourceIPAddress:172.16.12.0,SourceNetMask:255.255.0.0]", {path: now}, {path: 1});

Because it might be some attributes I dont want to change need to stay like they are.
So I want to know how i can add a voiceProfile with a spezific ID, but after deleting an object and adding a new one with the “.*.”-wildcard, genieacs is not using a used ID. The result is, I get a new object with a new ID… this leads to a result like this:

* InternetGatewayDevice.Services.VoiceService.2.VoiceProfile.1.Line.1.SIP.X_AVM-DE_AuthName  
* InternetGatewayDevice.Services.VoiceService.2.VoiceProfile.10.Line.1.SIP.X_AVM-DE_AuthName

But I want the new profile to have the ID “2”, instead it creates an object with the ID “10”…
I don’t understand why is that so? And how can I change this behaviour?

How I want it to be is something like this:

//example data that will be passed to the script
let prov_data = {
    "voip": {
        "vlan": 6,
        "sip_account": [
            {
                "number": "1001",
                "area_code": "7171",
                "sip_user": "1000001",
                "domain": "sip.domain.de",
                "password": "super_secret_1",
                "status": "active",
                "cancelled_on": null
            },
            {
                "number": "1002",
                "area_code": "7171",
                "sip_user": "1000001",
                "domain": "sip.domain.de",
                "password": "super_secret_2",
                "status": "active",
                "cancelled_on": null
            }
        ]
    },
    "router": {
        "product": "FRITZ!Box 7530",
        "cwmp": "00040E-*****",
        "serial_no": "*********",
        "genie_acs_device_id": "00040E-FRITZ%21Box-**********"
    },
    "options": [
        "option_domain_sip_de",
        "option_private",
        "option_ds_priv"
    ],
    "internet": {
        "vlan": 7,
        "connection_mode_identifier": "ds_priv",
        "bandwidth_down": 100000,
        "bandwidth_up": 50000,
        "protocol": "PPPoE",
        "pppoe_username": "20000001",
        "pppoe_password": "super_secret_internet_password"
    }
}

log("----------------------------------");
log("----------------------------------");

let path = "InternetGatewayDevice.Services.VoiceService.2.VoiceProfile."

// Number of Profiles
let device_n_profiles = declare("InternetGatewayDevice.Services.VoiceService.2.VoiceProfileNumberOfEntries", { value: 1 }, null)
let n_diff = prov_data.voip.sip_account.length - device_n_profiles.value[0];
log("");
log(`device_n_profiles = ${device_n_profiles.value[0]}`);
log(`sip_accounts = ${prov_data.voip.sip_account.length}`);
log(`n_diff = ${n_diff}`)
log("");

if (n_diff > 0) {
  // TODO: add profile
  for (let i = device_n_profiles.value[0]; i < prov_data.voip.sip_account.length; i++) {
    declare(path + i, {path: Date.now()}, {path: prov_data.voip.sip_account.length});
  }
} else if (n_diff < 0) {
  // TODO: remove profile
  for (let i = prov_data.voip.sip_account.length; i < device_n_profiles; i++) {
    clear(path + i, Date.now());
  }
}

Next step I want to do is to iterate through the data and update the instances on the device…
something like so:

for (var i = 1; i <= prov_data.voip.sip_account.length; i++) {
    declare(path + [i] + ".Enable:", { path: Date.now() }, { value: "Enable" })
    declare(path + [i] + ".SIP.RegistrarServer:", { path: Date.now() }, { value: prov_data.voip.sip_account[i - 1].domain })
    declare(path + [i] + ".Line.1.SIP.AuthUserName:", { path: Date.now() }, { value: prov_data.voip.sip_account[i - 1].sip_user })
    declare(path + [i] + ".Line.1.SIP.AuthPassword:", { path: Date.now() }, { value: prov_data.voip.sip_account[i - 1].password })
    declare(path + [i] + ".Line.1.SIP.X_AVM-DE_CountryCode:", { path: Date.now() }, { value: "49" })
    declare(path + [i] + ".Line.1.SIP.X_AVM-DE_OKZ:", { path: Date.now() }, { value: prov_data.voip.sip_account[i - 1].area_code })
    declare(path + [i] + ".Line.1.DirectoryNumber:", { path: Date.now() }, { value: prov_data.voip.sip_account[i - 1].number })
    declare(path + [i] + ".RTP.X_AVM-DE_tx_packetsize_in_ms", { path: Date.now() }, { value: "20" })
    declare(path + [i] + ".X_AVM-DE_GUI_ReadOnly", { path: Date.now() }, { value: "true" })
    declare(path + [i] + ".X_AVM-DE_route_always_over_internet", { path: Date.now() }, { value: "false" })
}

This is what I want to do, but it’s not working as expected yet.
Also:
Is this good or bad practice?
Are there solutions to this? (I couldn’t find anything like this anywhere)
How would you guys handle voiceProfiles?

Thanks for help :slight_smile:

The ACS has no say in what the instance number will be. You’re script is going to have to deal with the fact that instance numbers are unpredictable.

I’d like to point out though that following line:

declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[]", null, {path: 0});

will not immediately delete all instances. It’ll just mark them for deletion until you commit(). The two lines that follow ensure that two of the available instances matching the given alias (e.g. “SourceIPAddress:192.168.1.0,SourceNetMask:255.255.255.0”) will be kept. So I think you can achieve what you want by choose the right keys for the alias.

Alternatively, specify the exact number of instances you need and apply your config in order.

1 Like

I’m not sure if I understand it right, how to use the aliases.

why do we need the ip or network mask?
I use the preset to trigger the script for a CPE via the conditions.
Or is it wrong to use a preset for every CPE?

can you provide an example on how to add one voice profile for one CPE?

Okey I got it to work, but there is one issue…

my script is always executed twice.
I noticed there is a “GetParameterNames” and “GetParameterValues” Request while processing the script first time. Which makes no sense to me yet.
The ACS requests are while iterating my update profiles function.

ok this is normal, In the docs is written:

Please note, provision scripts will execute multiple times until GenieACS detects no more changes. This is normal behavior.

But I dont see the need of executing it twice every time.
Maybe I’m triggering something, that leads to that behaviour, I’m not sure…

Can you give me a hint on where I can find the docs for how and when this gets triggered?

Here is my script so far (maybe I have overseen something)

let serial = declare("InternetGatewayDevice.DeviceInfo.SerialNumber", { value: 1 }).value[0];
log("Serialnumber: " + serial);
log('');

let prov_data = {
    "voip": {
        "vlan": 6,
        "sip_account": [
            {
                "number": "1001",
                "area_code": "7171",
                "sip_user": "1000001",
                "domain": "sip.domain.de",
                "password": "super_secret_1",
                "status": "active",
                "cancelled_on": null
            },
            {
                "number": "1002",
                "area_code": "7171",
                "sip_user": "1000001",
                "domain": "sip.domain.de",
                "password": "super_secret_2",
                "status": "active",
                "cancelled_on": null
            }
        ]
    },
    "router": {
        "product": "FRITZ!Box 7530",
        "cwmp": "00040E-************",
        "serial_no": "************",
        "genie_acs_device_id": "00040E-FRITZ%21Box-************"
    },
    "options": [
        "option_domain_sip_dbn_de",
        "option_private",
        "option_ds_priv"
    ],
    "internet": {
        "vlan": 7,
        "connection_mode_identifier": "ds_priv",
        "bandwidth_down": 100000,
        "bandwidth_up": 50000,
        "protocol": "PPPoE",
        "pppoe_username": "20000001",
        "pppoe_password": "super_secret_internet_password"
    }
}

let path = "InternetGatewayDevice.Services.VoiceService.2.VoiceProfile."

// Lectures
// https://www.broadband-forum.org/technical/download/TR-069.pdf#page=82&zoom=100,0,477 (Kap. A.2.2.2 : Instance Alias Identifier)
// https://www.broadband-forum.org/technical/download/TR-069.pdf (Appendix II. Alias-Based Addressing Mechanism – Theory of Operations)
// https://www.broadband-forum.org/technical/download/TR-069.pdf (Kap. 3.6.1 : Alias-Based Addressing Mechanism Requirements)

let ACS_ASSIGNED_INSTANCE_ALIAS = `X_AVM-DE_alias_head_number`

for (let [index, account] of Object.entries(prov_data.voip.sip_account)) {
    let elementExists = declare(path + `[Line.1.DirectoryNumber:${account.number}]` + ".Line.1.DirectoryNumber", { value: 1, object: 1 }, null);
    log(`process profile: ${index}: ${account.number}`)
    log(`element: ${elementExists.value}`)
    if (elementExists.value) {
        updateProfile(account);
    } else {
        addNewProfile(account);
    }
}

deleteProfile("");// delete blank numbers on CPE

// try to update NumberOfEntries
declare("InternetGatewayDevice.Services.VoiceService.2.VoiceProfileNumberOfEntries", { path: Date.now() }, { value: prov_data.voip.sip_account.length });

function updateProfile(account) {
    log(`update profile: DirectoryNumber: ${account.number}`);
    let identifier = path + `[Line.1.DirectoryNumber:${account.number}]`;

    declare(identifier + ".Enable:", { path: Date.now() }, { value: "Enable" })
    declare(identifier + ".SIP.RegistrarServer:", { path: Date.now() }, { value: account.domain })
    declare(identifier + ".Line.1.SIP.AuthUserName:", { path: Date.now() }, { value: account.sip_user })
    declare(identifier + ".Line.1.SIP.AuthPassword:", { path: Date.now() }, { value: account.password })
    declare(identifier + ".Line.1.SIP.X_AVM-DE_CountryCode:", { path: Date.now() }, { value: "49" })
    declare(identifier + ".Line.1.SIP.X_AVM-DE_OKZ:", { path: Date.now() }, { value: account.area_code })
    declare(identifier + ".Line.1.DirectoryNumber:", { path: Date.now() }, { value: account.number })
    declare(identifier + ".RTP.X_AVM-DE_tx_packetsize_in_ms", { path: Date.now() }, { value: "20" })
    declare(identifier + ".X_AVM-DE_GUI_ReadOnly", { path: Date.now() }, { value: "true" })
    declare(identifier + ".X_AVM-DE_route_always_over_internet", { path: Date.now() }, { value: "false" })
}

function addNewProfile(account) {
    log(`add profile: DirectoryNumber: ${account.number}`);
    return declare(path + `[` +
        `Line.1.SIP.${ACS_ASSIGNED_INSTANCE_ALIAS}:${account.number},` +
        `Enable:Enabled,` +
        `SIP.RegistrarServer:${account.domain},` +
        `Line.1.SIP.AuthUserName:${account.sip_user},` +
        `Line.1.SIP.AuthPassword:${account.password},` +
        `Line.1.SIP.X_AVM-DE_CountryCode:49,` +
        `Line.1.SIP.X_AVM-DE_OKZ:${account.area_code},` +
        `Line.1.DirectoryNumber:${account.number},` +
        `RTP.X_AVM-DE_tx_packetsize_in_ms:20,` +
        `X_AVM-DE_GUI_ReadOnly:true,` +
        `X_AVM-DE_route_always_over_internet:false` +
        `]`, { path: Date.now() }, { path: 1 });
}

function deleteProfile(acc_number) {
    log(`delete profile: DirectoryNumber: ${acc_number}`);
    let identifier = path + `[Line.1.DirectoryNumber:${acc_number}]`;
    return declare(identifier, null, { path: 0 })
}

return;

This should explain it: http://docs.genieacs.com/en/latest/administration-faq.html#duplicate-log-entries-when-using-log-function

1 Like

this is exactly it, thank you!