GenieACS: Virtual Params
No GenieACS, a árvore do device mostra o que o CPE reportou: serial, OUI, modelo, tags. Só que no dia a dia o analista não fica pensando em serial: pensa em código de conexão, contrato, aquele identificador que o atendimento já usa.
Virtual Parameter é um parâmetro que não existe no CPE. O ACS calcula (ou monta) o valor em JavaScript e mostra na UI como se fosse nativo, sob VirtualParameters.*.
Neste post a gente cria três exemplos que eu validei no lab (GenieACS 1.2.16 + CPE virtual):
- OUI: espelha
DeviceID.OUI(primeira prática, simples) - CodigoConexao: monta
CURSO-{serial}(padrão “código do cliente” na tela) - WAN: IPv4 de internet padronizado (TR-098
.1/.2/.3e TR-181), sem if por vendor
Pré-requisitos:
Instalação GenieACS: Bare Metal x Docker: ACS no ar (UI :3000, CWMP :7547, NBI :7557). No caminho Docker do curso, o CPE virtual (:8080) já vem no compose.
preencha uma vez · Copiar já sai pronto · clique no token para voltar
IP público: curl ifconfig.me
Por que isso facilita o dia a dia
Sem virtual param, o analista abre o device, copia o serial e vai no ERP/billing achar o cliente. Com CodigoConexao na árvore, o valor útil já está no GenieACS: filtro, olho na lista, atendimento.
O mesmo vale para IP da WAN. Huawei manda PPPoE em …WANPPPConnection.1; ZTE às vezes sobe internet em .2 e deixa .1 para outro serviço; já o CPE em TR-181 trabalha com Device.IP.Interface.* (não com InternetGatewayDevice). Se cada script ou tela fica caçando path por modelo, alguém sempre quebra. Com VirtualParameters.WAN, todo mundo lê um campo.
O OUI sozinho parece “bobinho” (o campo já existe em DeviceID). Serve para aprender o mecanismo sem lógica: se o espelho funciona, o caminho até o device está certo. Depois você troca o script por algo do seu provedor.
Virtual Param ≠ Provision
- Provision: age no CPE / no device (declare, set, tag). No lab: coloca a tag
provisionado. - Virtual Parameter: calcula no ACS. O CPE nunca “tem” esse parâmetro; a UI é que mostra.
Cuidado clássico: criar o VP em Admin e achar que acabou. A documentação do GenieACS é clara: o valor precisa ser buscado (manual ou via preset/provision). Sem o declare("VirtualParameters.…"), a definição existe e o device fica sem o campo.
O que vamos montar
- Três virtual parameters via NBI (JavaScript puro no body do
curl) - Uma provision que pede o fetch dos VPs (e marca a tag)
- Preset no CPE virtual (
ProductClass = VirtualRouterLAB) - Inform / Connection Request → valor na árvore do device
Se preferir um atalho depois de entender o fluxo, o pacote do lab tem ./scripts/seed-curso.sh: é exatamente esses recursos. Aqui a gente faz na mão.
1) Conferir o ACS e o CPE
UI:
http://SEU_IP:3000
Lab Docker do curso: login admin / admin.
Painel do roteador virtual:
http://SEU_IP:8080
NBI responde (sem auth no lab padrão):
curl -s http://SEU_IP:7557/devices/ | head
Se a NBI estiver com Basic Auth (bare metal com nginx, ou lab com NBI_AUTH_ENABLED=true), acrescente -u usuario:senha nos curl abaixo.
No painel do CPE (:8080), aba TR-069: ACS URL http://SEU_IP:7547/ → salvar e conectar. O device precisa aparecer na UI antes (ou na sequência) do Connection Request.
2) Criar o Virtual Parameter OUI
Body do PUT é JavaScript, não JSON. O script lê um parâmetro do device e devolve o valor tipado:
curl -X PUT "http://SEU_IP:7557/virtual_parameters/OUI" --data-binary @- <<'EOF'
const oui = declare("DeviceID.OUI", {value: 1}).value[0];
return {writable: false, value: [oui, "xsd:string"]};
EOF
O que importa nessa primeira peça:
declare("DeviceID.OUI", {value: 1}): pede o OUI com frescor (TTL 1 segundo no cache do GenieACS)return { writable: false, value: [valor, "xsd:string"] }: formato que a UI espera
Confira se gravou:
curl -s http://SEU_IP:7557/virtual_parameters/OUI
Na UI: Admin → Virtual parameters: deve listar OUI.
3) Criar o Virtual Parameter CodigoConexao
curl -X PUT "http://SEU_IP:7557/virtual_parameters/CodigoConexao" --data-binary @- <<'EOF'
const serial = declare("DeviceID.SerialNumber", {value: 1}).value[0];
return {writable: false, value: ["CURSO-" + serial, "xsd:string"]};
EOF
No lab, com serial LAB000001, o valor esperado depois da sessão é:
CURSO-LAB000001
No seu provedor você troca o prefixo CURSO- pelo padrão interno (código do contrato, ID do CRM, etc.). A lógica é a mesma: pegar um campo confiável do Inform e montar o identificador que o time já usa no WhatsApp/ERP.
4) Criar o Virtual Parameter WAN (padronizar IP de internet)
Nome do VP não pode ter ponto: por isso é WAN, e na árvore aparece VirtualParameters.WAN (não wan.1).
A ideia: varrer com wildcard as árvores comuns, preferir PPPoE com ConnectionStatus = Connected (no lab o internet está no .2; o .1 fica Disconnected), depois cair em WANIPConnection e, por último, TR-181.
curl -X PUT "http://SEU_IP:7557/virtual_parameters/WAN" --data-binary @- <<'EOF'
// Padroniza o IPv4 da WAN de internet.
// Vendor A: WANPPPConnection.1 | Vendor B: .2/.3 | TR-181: Device.IP.Interface.*
// Você consulta só VirtualParameters.WAN, sem if por árvore.
let ip = "";
const fresh = {value: Date.now()};
const pppIp = declare(
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANPPPConnection.*.ExternalIPAddress",
fresh
);
const pppSt = declare(
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANPPPConnection.*.ConnectionStatus",
fresh
);
const ipconn = declare(
"InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.ExternalIPAddress",
fresh
);
const d181 = declare("Device.IP.Interface.*.IPv4Address.*.IPAddress", fresh);
function statusMap(iter) {
const m = {};
if (!iter.size) return m;
for (let p of iter) m[p.path] = p.value[0];
return m;
}
function firstConnectedOrAny(ips, statuses) {
if (!ips.size) return "";
let fallback = "";
for (let p of ips) {
const v = p.value[0];
if (!v) continue;
const stPath = p.path.replace(/\.ExternalIPAddress$/, ".ConnectionStatus");
const st = statuses[stPath] || "";
if (st === "Connected") return v;
if (!fallback) fallback = v;
}
return fallback;
}
function firstNonEmpty(iter) {
if (!iter.size) return "";
for (let p of iter) {
if (p.value[0]) return p.value[0];
}
return "";
}
ip = firstConnectedOrAny(pppIp, statusMap(pppSt));
if (!ip) ip = firstNonEmpty(ipconn);
if (!ip) ip = firstNonEmpty(d181);
return {writable: false, value: [ip, "xsd:string"]};
EOF
No lab (CPE virtual), o data model tem de propósito:
…WANPPPConnection.1→Disconnected, sem IP…WANPPPConnection.2→Connected, IP203.0.113.50Device.IP.Interface.1.IPv4Address.1.IPAddress→198.51.100.10(TR-181 “isca”, não deve ganhar se o PPPoE Connected existir)
Depois da sessão, o valor validado foi:
VirtualParameters.WAN = 203.0.113.50
Ou seja: pegou o .2 Connected e não o IP da árvore TR-181. Na prática, filtro e automação passam a olhar VirtualParameters.WAN: sem montar query por path de ZTE vs Huawei vs TR-181.
Obs.: em CPE real o wildcard * também cobre .3, .4… Ajuste a ordem (PPP → IPConnection → Device.IP) se o seu parque for majoritariamente TR-181 puro.
5) Provision que “liga” os VPs ao device
Só criar em Admin não coloca o valor no device. A provision pede o fetch:
curl -X PUT "http://SEU_IP:7557/provisions/lab-curso-bootstrap" --data-binary @- <<'EOF'
const serial = declare("DeviceID.SerialNumber", {value: 1}).value[0];
log("Provision curso, serial " + serial);
declare("Tags.provisionado", null, {value: true});
declare("VirtualParameters.CodigoConexao", {value: 1});
declare("VirtualParameters.OUI", {value: 1});
declare("VirtualParameters.WAN", {value: 1});
EOF
Quatro coisas nessa provision:
log(...): aparece nos logs do GenieACS (útil pra debug)Tags.provisionado: marca visual na UIdeclare("VirtualParameters.…", {value: 1}): dispara o cálculo de cada VP na sessão- Incluir o
WANna mesma provision evita um preset só para isso
6) Preset: quando a provision roda
O preset não calcula o VP. Ele só diz: “nesse device, rode essa provision”.
Para o CPE virtual do lab (ProductClass = VirtualRouterLAB):
curl -X PUT "http://SEU_IP:7557/presets/lab-virtual-router" \
-H "Content-Type: application/json" \
--data '{"weight": 1, "channel": "default", "precondition": "{\"DeviceID.ProductClass\":\"VirtualRouterLAB\"}", "events": {}, "configurations": [{"type": "provision", "name": "lab-curso-bootstrap"}]}'
E o canal bootstrap (primeiro Inform com evento 0 BOOTSTRAP):
curl -X PUT "http://SEU_IP:7557/presets/lab-virtual-router-bootstrap" \
-H "Content-Type: application/json" \
--data '{"weight": 5, "channel": "bootstrap", "precondition": "{\"DeviceID.ProductClass\":\"VirtualRouterLAB\"}", "events": {"0 BOOTSTRAP": true}, "configurations": [{"type": "provision", "name": "lab-curso-bootstrap"}]}'
Precondition em JSON dentro de string é chato, mas é o formato da NBI. Errou aspas, o preset não casa e a provision não roda (sintoma: device sobe sem tag e sem VirtualParameters).
Atalho equivalente (mesmo conteúdo):
cd /opt/tr069-lab # ou a pasta do lab
NBI_HOST=http://SEU_IP:7557 ./scripts/seed-curso.sh
7) Disparar a sessão e ver o valor
Se o CPE já está conectado, acorde com Connection Request (credencial padrão do lab: acs / acs):
curl -u acs:acs http://SEU_IP:7548/tr069/connection-request
Espere uns segundos e consulte pela NBI:
curl -s "http://SEU_IP:7557/devices/?projection=DeviceID.SerialNumber,DeviceID.OUI,VirtualParameters,Tags" | python3 -m json.tool
No lab validado, o device ACAD01-VirtualRouterLAB-LAB000001 veio com:
VirtualParameters.CodigoConexao = CURSO-LAB000001
VirtualParameters.OUI = ACAD01
VirtualParameters.WAN = 203.0.113.50
Na UI: abra o device → busque CodigoConexao, WAN ou navegue em VirtualParameters.
Apagou o device no ACS? A definição do VP em Admin permanece. O valor no device só volta depois de nova sessão CWMP com a provision ativa, reconecte no :8080 se precisar e dispare o CR de novo.
O que costuma falhar
- Criou o VP e não colocou
declare("VirtualParameters.X", {value: 1})na provision → Admin mostra o script, device não. - Preset com precondition errada (ProductClass diferente do CPE) → provision nunca roda.
- Body do
curlem JSON por engano → NBI aceita de forma estranha ou o script não executa. Use--data-binarycom o JS puro. LAB_PUBLIC_IPerrado no Docker → painel do CPE mostra URL inútil; Inform não sobe. Veja o post de instalação.- VP
WANveio vazio → o CPE ainda não tem ExternalIP na árvore (refresh/GetParameterValues) ou o status não éConnectede não há fallback. Olhe…WANPPPConnection.*.ExternalIPAddressno device antes de culpar o script. - NBI 401 → faltou
-u(auth ligada).
Fechando
Virtual param é ferramenta de operação: código de conexão na tela, IP de WAN estável, sem o CPE “saber” de billing e sem cada um decorar path de fabricante. Comece pelo espelho (OUI), feche o ciclo com provision + preset, depois padronize o que o time mais pergunta (CodigoConexao, WAN, sinal, PPPoE…).
Stack e portas: Instalação GenieACS: Bare Metal x Docker. Se algo não bater no seu lab, deixa um comentário no post.
Gostou do conteúdo? Veja os cursos em aberto ou entre na lista de interesse. Os treinamentos são ao vivo e 100% práticos.