# Welcome

Welcome to Oktopus Controller official documentation site.

Here you'll find helpfull content, tutorials and insights about the Oktopus Controller, along with that we also want to dive deep into the details of the [USP](/usp-tr-369/introduction) and [CWMP](/cwmp-tr-069/introduction) protocols.

The main goal is to provide one platform to manage multiple devices of different vendors. If they support USP or CWMP protocol, than Oktopus is the right choice.

{% hint style="info" %}
This site is a document always in construction. Your help is very appreciated to improve it and help us to provide high quality content about USP, CWMP and Oktopus Controller. If you wish to contribute, relate to the next section.
{% endhint %}

## Contribute

Oktopus Controller is Open Source as is this documentation. Fell free to make contributions to the [project](https://github.com/OktopUSP/oktopus) and this [docs](https://github.com/OktopUSP/docs).

## Support

Our team is available to support Enterprise Edition clients through [email](mailto:support@oktopus.app.br). If you have further questions about the Community Edition **we are available at** [**Slack**](https://join.slack.com/t/oktopustr-369/shared_invite/zt-1znmrbr52-3AXgOlSeQTPQW8_Qhn3C4g)**, along with other members of the community**.


# About

Oktopus is an open-source USP Controller and CWMP-compatible multi-vendor management platform for CPEs and IoTs. Any device that follows the TR-369 or TR-069 protocol can be managed. Our main objective is to unify device management into a single software solution with rich insights and configuration capabilities, improving the quality of experience and services provided by a CSP or ISP while reducing the costs of technical support, operations, and maintenance.

Learn about the [architecture](/getting-started/community-edition/architecture) first, get familiar with the [installation prerequisites](/getting-started/community-edition/installation), and then finally, step through how to [install the software](/getting-started/community-edition/installation) in your environment.

### Community vs Enterprise

This table compares the features available in Oktopus Community Edition versus Enterprise Edition.

| Feature                                       | Community | Enterprise         |
| --------------------------------------------- | --------- | ------------------ |
| **Core Device Management**                    |           |                    |
| USP (TR-369) & CWMP (TR-069) Protocols        | ✅         | ✅                  |
| Multiple Transports (MQTT, STOMP, WebSockets) | ✅         | ✅                  |
| Device Discovery & Configuration              | ✅         | ✅                  |
| Custom Message Templates                      | ✅         | ✅                  |
| Overview Dashboard                            | ✅         | ✅ (with more data) |
| Basic User Management                         | ✅         | ✅                  |
| REST API                                      | ✅         | ✅                  |
| **Enterprise Features**                       |           |                    |
| Audit Trail                                   | ❌         | ✅                  |
| Granular Permissions (RBAC)                   | ❌         | ✅                  |
| Multi-Tenancy                                 | ❌         | ✅                  |
| Abstracted Device Management UI               | ❌         | ✅                  |
| Zero Touch Provisioning                       | ❌         | ✅                  |
| Network Automation                            | ❌         | ✅                  |
| Mass Actions                                  | ❌         | ✅                  |
| Devices Geolocation                           | ❌         | ✅                  |
| QoE Analysis                                  | ❌         | ✅                  |
| AI Assistant                                  | ❌         | ✅                  |
| **Support & Services**                        |           |                    |
| Community Support (Slack)                     | ✅         | ✅                  |
| Email Support                                 | ❌         | ✅                  |
| Device Homologation                           | ❌         | ✅                  |
| Professional Services                         | ❌         | ✅                  |
| **License**                                   |           |                    |
| Open Source                                   | ✅         | ❌                  |
| Cost                                          | Free      | Paid               |


# Community Edition

Suited for DIY people, developers and enthusiasts

## Dashboard

Check the overall performance of the system, and get insights about the devices as their type, total quantity and status.

<figure><img src="/files/R42DzZwq9u6MsQkmURfr" alt=""><figcaption><p>Overview Section</p></figcaption></figure>

## Devices

Table list with all the devices connected to Oktopus, independent of the protocol used for management, all of them will appear in this list, with options to filter for specific devices characteristics, and remove or add table fields to view.

<figure><img src="/files/itWpA92wU0A3gVmcrDOZ" alt=""><figcaption><p>Devices Section</p></figcaption></figure>

### Parameters Discovery

Access the device you wish and discover the data model dinamically.

<figure><img src="/files/laOWm8GHfZgdjFrSn0H1" alt=""><figcaption><p>Data Model Discovery</p></figcaption></figure>

### Custom Messages

Send custom USP or CWMP native packets, It is possible to create and save messages templates to be used later, according to each protocol specification.

<figure><img src="/files/BdAuXFZCNSk6PmdRDQWn" alt=""><figcaption><p>Message Template Saved</p></figcaption></figure>

<figure><img src="/files/F5uoXtO35UV3PGeDISye" alt=""><figcaption><p>Create Message Template</p></figcaption></figure>

### Credentials

This section defines credentials to be used by the devices to connect to Oktopus Controller, those credentials are going to be attached to the ACS, MQTT, STOMP and Websockets instances that will now check if the device has the correct keys.

For this to work you need to enable this feature through environment variables in each server/broker protocol you want to have authentication enabled.

<figure><img src="/files/7puHzjbyLpAaYe2WAesD" alt=""><figcaption><p>Credentials Section</p></figcaption></figure>

## Users

There are two levels of users availables in the Community Edition:

* *Admin*: Have access to the whole software platform, including the rights to remove/add users and devices credentials.
* *User*: Can access devices and configure the same, view users, dashboard information and credentials, but is not allowed to remove/add other users and devices credentials.

<figure><img src="/files/QjV9qtux2LQKROtuk4DF" alt=""><figcaption><p>Users Management Page</p></figcaption></figure>


# Installation

> The following tutorials don't cover all possible configurations and deployment modes of Oktopus Controller, many adjusts as TLS encryption of MTPs and NATS are not documented yet, although the software already supports it. Your help is welcome to help us cover this topics, and soon we hope to be launching content related to encryption, USP and CWMP best practices and different possibilites to install Oktopus as in a cloud provider, on-premises, or hybrid.

## Quick Start

If you're a developer or someone who just wants to try Oktopus and get a better understanding of CWMP and/or USP standards, don't waste time. Have the software running with one command:

```
wget https://github.com/OktopUSP/oktopus/archive/refs/heads/main.zip && unzip main && cd oktopus-main/deploy/compose && COMPOSE_PROFILES=nats,controller,cwmp,mqtt,stomp,ws,adapter,frontend docker compose up -d
```

The web interface will become accessible at **port 80**:

<figure><img src="/files/s0MK941wX4HIMhJh2maM" alt=""><figcaption><p>Oktopus web first acces</p></figcaption></figure>


# Standalone

The standalone mode installs all the software components into one server. It's pretty straighforward, but becomes a single point of failure. Anyway, it should be enough for small-medium sized workloads.

## Requirements

* docker
* docker compose
* wget
* unzip

Have the services ports available internally or public accordingly to Oktopus [network requirement section](/getting-started/community-edition/network). Keep in mind it's always advisable to have a firewall in front of your server and have controll of the traffic policies.

This tutorial was tested against OS Ubuntu 24.04, but it must work for other linux systems too.

## Installation

First of all, let's download Oktopus codebase:

```
wget https://github.com/OktopUSP/oktopus/archive/refs/heads/main.zip && unzip main
```

Then go into the deploy/compose folder:

```
cd oktopus-main/deploy/compose
```

Edit ".env.controller" file last line with a secret value, this key will be used to sign JWTs used in Oktopus users authentication process.

```
nano .env.controller
```

{% code title=".env.controller" %}

```
MONGO_URI=mongodb://mongo_usp:27017
NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222
NATS_ENABLE_TLS="true"
CLIENT_CRT=/tmp/nats/config/cert.pem
CLIENT_KEY=/tmp/nats/config/key.pem
SERVER_CA=/tmp/nats/config/rootCA.pem
SECRET_API_KEY=<super-secret-key>
```

{% endcode %}

Once you've done the steps above, still inside "oktopus-main/deploy/compose" folder, run:

```
COMPOSE_PROFILES=nats,controller,cwmp,mqtt,stomp,ws,adapter,frontend,portainer docker compose up -d
```

Congratulations! Oktopus Web interface will be available at **port 80**.


# High Availability

For deployments with failover and advanced container management/orchestration, Kubernetes and it's flavors are the go-to option. This section aims to provide a example deployment that may need to be changed according to your environment specifics, as hardware/software Load Balancers, cluster nodes, firewall, bare-metal or cloud environment.

## Installation

### Download Files

```
git clone https://github.com/OktopUSP/oktopus
export DEPLOYMENT_PATH=oktopus/deploy/kubernetes
```

### HAProxy Ingress Controller

```
helm install haproxy-kubernetes-ingress haproxytech/kubernetes-ingress \
  --create-namespace \
  --namespace haproxy-controller \
  --set controller.kind=DaemonSet \
  --set controller.daemonset.useHostPort=true
```

### MongoBD

```
# Mongo DB Operator at mongodb namespace
helm repo add mongodb https://mongodb.github.io/helm-charts

helm install community-operator mongodb/community-operator --namespace mongodb --create-namespace

# Mongo DB ReplicaSet
export DEPLOYMENT_PATH=oktopus/deploy/kubernetes

kubectl apply -f $DEPLOYMENT_PATH/mongodb.yaml -n mongodb

# Check Installation
kubectl get pods -n mongodb
```

### NATS Server

```
# Download the NATS charts
helm repo add nats https://nats-io.github.io/k8s/helm/charts/

# Install NATS with Jetstream Enabled
helm install nats nats/nats --set config.jetstream.enabled=true
```

### Oktopus

```
kubectl apply -f $DEPLOYMENT_PATH/mqtt.yaml
kubectl apply -f $DEPLOYMENT_PATH/mqtt-adapter.yaml
kubectl apply -f $DEPLOYMENT_PATH/adapter.yaml
kubectl apply -f $DEPLOYMENT_PATH/controller.yaml
kubectl apply -f $DEPLOYMENT_PATH/socketio.yaml
kubectl apply -f $DEPLOYMENT_PATH/frontend.yaml
kubectl apply -f $DEPLOYMENT_PATH/ws.yaml
kubectl apply -f $DEPLOYMENT_PATH/ws-adapter.yaml
```

### Checking cluster status:

```bash
kubectl get pods
kubectl get svc
```


# FAQ

Frequently asked questions about installation.

#### Followed the installation guide but user register page does not appear?

Check if you're connected to Oktopus through the port 80 instead of 300, then make sure tha MongoDB is running, it has some issues depending on the CPU architecture which makes it crash, so if that's the case with you, change the [docker-compose file](https://github.com/OktopUSP/oktopus/blob/main/deploy/compose/docker-compose.yaml#L41) to a different MongoDB version compatible with the server hardware.

#### What is Oktopus default user and password?

Oktopus has no default user and password, in the first run of the software it should appear a user register page, if it didn't appear, follow the instructions mentioned in the question above.


# Architecture

Oktopus Controller backend is made up of some microservices written in [Go](https://go.dev/) because of the language versatility, speed, and small resource usage footprint. All the microservices communicate with each other through [NATS](https://nats.io/), which is a robust message system, really fast, built for IoT and edge applications. The database picked is [MongoDB](https://www.mongodb.com/), which makes possible to have flexible data, saved in the right manner based on our needs.

The frontend is made with the most modern frameworks and design system available in the market. It has the [Material UI ](https://mui.com/material-ui/getting-started/)components lib integrated with [NextJS](https://nextjs.org/), which ensures a smooth single page app experience.

<figure><img src="/files/dAqQ2gk32uL1Q1tDHjKD" alt=""><figcaption><p>High-Level Components</p></figcaption></figure>

All the different protocols used to manage the devices are abstracted to NATS message system, this allow us to have different components/services deployed in different servers, and locations, as long as the connection to NATS cluster is maintained.

Software components are deployed within [Docker](https://www.docker.com/) containers. The most straigthforward way to install Oktopus Controller is using docker compose. For high availability production environments we suggest to use Kubernetes.

<figure><img src="/files/qoYsR5CB1v44K8uKXC75" alt=""><figcaption><p>Microservices Overview</p></figcaption></figure>


# Network

Oktopus Controller ports and routes:

<table><thead><tr><th>Endpoint</th><th width="179">Network Protocol</th><th width="280">Application</th><th>Expose</th></tr></thead><tbody><tr><td>:9292/acs</td><td>HTTP</td><td>Auto Configuration Server (ACS)</td><td>Public</td></tr><tr><td>:8883</td><td>MQTTS</td><td>MQTT MTP with TLS</td><td>Public</td></tr><tr><td>:1883</td><td>MQTT</td><td>MQTT MTP without TLS</td><td>Public</td></tr><tr><td>:61613</td><td>STOMP</td><td>STOMP MTP</td><td>Public</td></tr><tr><td>:8080</td><td>Websockets</td><td>Websockets MTP</td><td>Public</td></tr><tr><td>:9443</td><td>HTTP</td><td>Portainer</td><td>Internal</td></tr><tr><td>:8004</td><td>HTTP</td><td>File Server</td><td>Internal</td></tr><tr><td>:80</td><td>HTTP</td><td>Nginx</td><td>Public</td></tr><tr><td>:3000</td><td>HTTP</td><td>Frontend</td><td>Internal</td></tr><tr><td>:8000</td><td>HTTP</td><td>Controller REST API</td><td>Public</td></tr><tr><td>:27017</td><td></td><td>MongoDB</td><td>Internal</td></tr><tr><td>:4222</td><td>NATS</td><td>NATS Message System</td><td>Internal</td></tr></tbody></table>

Make sure that you have all the ports above available.


# Developer Guide

### Project Architecture

#### High-Level Structure

```
oktopus/
├── agent/          # USP Agent configuration and examples
├── backend/        # Core backend services
│   └── services/
│       ├── controller/    # Main controller service
│       └── mtp/          # Message Transfer Protocol adapters
├── frontend/       # Next.js web application
├── deploy/         # Deployment configurations
└── docs/           # GitBook documentation
```

#### Backend Architecture

The backend is built with Go and follows a modular service architecture:

**Controller Service**

Located in `backend/services/controller/`, this is the core service that manages devices and handles protocol communications.

**Key Components:**

* **API Layer** (`internal/api/`): REST API endpoints for device management
  * `api.go`: Main API router and middleware setup
  * Device message handlers (GET, SET, ADD, DELETE operations)
* **CWMP Support** (`internal/cwmp/`): TR-069 protocol implementation
  * Handles CWMP-specific operations like GetParameterNames, SetParameterValues
* **Entity Layer** (`internal/entity/`): Core business entities and data models
* **Database Layer** (`internal/db/`): Database abstraction and persistence
* **Bridge** (`internal/bridge/`): Communication bridge between services

**MTP Adapters**

The Message Transfer Protocol adapters handle different communication protocols:

* **WebSocket Adapter** (`backend/services/mtp/ws-adapter/`): Implements WebSocket MTP for USP
* **MQTT Adapter**: Handles MQTT-based communication
* **STOMP Adapter**: Handles STOMP protocol

As mentioned in the USP MTP documentation, USP supports multiple MTPs including WebSockets, MQTT, and STOMP.

#### Frontend Architecture

The frontend is built with Next.js and located in `frontend/`. It provides a web interface for managing devices.

### Development Setup

#### Prerequisites

* **Go**: Version 1.23+
* **Node.js**: Version 18+
* **NATS**: Message broker for inter-service communication
* **Docker**: For containerized development (optional)

#### Building the Services

**Docker Development**

From the root `build/` directory, you can build all services at once:

```bash
cd build/
make build              # Build all services (backend + frontend)
make build-backend      # Build only backend services
make build-frontend     # Build only frontend
```

**Building Individual Microservices**

Each microservice has its own build directory with a Makefile and Dockerfile. The general pattern is:

```bash
cd backend/services/<service-name>/build/
make build
```

**Available Backend Services:**

1. **Controller Service** - Core device management

   ```bash
   cd backend/services/controller/build/
   make build
   ```

   * Built from: `cmd/controller/main.go`
   * Binary: `controller`
   * Base image: Alpine Linux
2. **ACS Service** - Auto Configuration Server (CWMP)

   ```bash
   cd backend/services/acs/build/
   make build
   ```

   * Built from: `cmd/acs/main.go`
   * Binary: `acs`
   * Base image: Alpine Linux
3. **MTP Adapters** - Message Transfer Protocol handlers

   **WebSocket Adapter:**

   ```bash
   cd backend/services/mtp/ws-adapter/build/
   make build
   ```

   * Built from: `cmd/ws-adapter/main.go`
   * Handles WebSocket connections for USP

   **WebSocket Service:**

   ```bash
   cd backend/services/mtp/ws/build/
   make build
   ```

   * Built from: `cmd/ws/main.go`

   **MQTT Adapter:**

   ```bash
   cd backend/services/mtp/mqtt-adapter/build/
   make build
   ```

   * Built from: `cmd/mqtt-adapter/main.go`
   * Handles MQTT connections for USP

   **MQTT Service:**

   ```bash
   cd backend/services/mtp/mqtt/build/
   make build
   ```

   * Built from: `cmd/mqtt/main.go`

   **STOMP Adapter:**

   ```bash
   cd backend/services/mtp/stomp-adapter/build/
   make build
   ```

   * Built from: `cmd/stomp-adapter/main.go`
   * Handles STOMP connections for USP

   **STOMP Service:**

   ```bash
   cd backend/services/mtp/stomp/build/
   make build
   ```

   * Built from: `cmd/stomp/main.go`

   **Generic Adapter:**

   ```bash
   cd backend/services/mtp/adapter/build/
   make build
   ```

   * Built from: `cmd/adapter/main.go`
4. **Utility Services**

   **Socket.IO Service:**

   ```bash
   cd backend/services/utils/socketio/build/
   make build
   ```

   * Node.js service for real-time communication
   * Base image: Node 16.20.2-alpine

   **File Server:**

   ```bash
   cd backend/services/utils/file-server/build/
   make build
   ```

   * Serves firmware files and other static content
5. **Bulk Data Collector**

   ```bash
   cd backend/services/bulkdata/http/build/
   make build
   ```

   * Built from: `cmd/http-bulk-collector/main.go`
   * Collects bulk data from devices \[WIP]

**Building the Frontend**

```bash
cd frontend/build/
make build
```

* Next.js application
* Node.js 18.18.0-alpine base image
* Production build with optimizations

### **Local Development**

**Backend Services (Go):**

```bash
# Navigate to the service directory
cd backend/services/controller  # or any other service

# Install dependencies
go mod download

# Run locally
go run cmd/controller/main.go  # adjust path for the specific service cmd/{service}/main.go

# Build binary
go build -o controller cmd/controller/main.go
# Run binary
./controller

# Run without build (for development purposes)
go run cmd/controller/main.go
```

**Frontend (Next.js):**

```bash
cd frontend

# Install dependencies
npm install

# Development server
npm run dev

# Production build
npm run build
npm run start
```

**Makefile Commands**

Each service's Makefile supports these commands:

* `make build` - Build Docker image
* `make push` - Push image to registry
* `make run` - Create and start container
* `make stop` - Stop running container
* `make remove` - Remove container
* `make delete` - Delete Docker image
* `make logs` - Show container logs
* `make bash` - Access container shell
* `make release` - Tag as latest and push

**Customization:**

```bash
# Build with custom Docker user
make build DOCKER_USER=myuser

# Build with specific tag
make build DOCKER_TAG=v1.0.0

# Use different shell for container access
make bash CONTAINER_SHELL=/bin/bash
```

#### Running the Services

The project includes:

* Build configurations in `build/Makefile`
* Docker Compose deployment in `deploy/compose/`
* Kubernetes deployment in `deploy/kubernetes/`

### Code Organization

#### Backend Service Structure

The controller service follows a clean architecture pattern:

```
backend/services/controller/
├── cmd/                    # Application entry points
├── internal/
│   ├── api/               # HTTP API handlers
│   ├── bridge/            # Service communication bridge
│   ├── config/            # Configuration management
│   ├── cwmp/              # CWMP protocol implementation
│   ├── db/                # Database layer
│   ├── entity/            # Domain entities
│   └── utils/             # Utility functions
└── main.go
```

#### Pull Request Process

1. Fork the repository and create a feature branch
2. Make your changes following the code style guidelines
3. Write clear commit messages describing what and why
4. Test thoroughly with both protocols if applicable
5. Update documentation if you're adding new features
6. Submit a pull request to the main repository

Include in your PR description:

* What problem does it solve?
* How was it tested?
* Any breaking changes?
* Screenshots (for UI changes)

### Understanding Key Concepts

#### Device Communication Flow

1. Device connects via MTP adapter (WebSocket/MQTT/STOMP for USP, or ACS connection for CWMP)
2. Adapter ingest and digest messages through NATS message broker
3. API endpoint at Controller processes the user requests
4. Message is queued and sent to the device
5. Response is awaited with timeout
6. Result is returned to the client

#### CWMP vs USP Implementation

The codebase supports both protocols:

**CWMP (TR-069)**:

* Synchronous request/response model
* XML-based RPC
* Direct parameter paths (e.g., `InternetGatewayDevice.LANDevice.*.WLANConfiguration.*`)
* See `internal/cwmp/` for implementation

**USP (TR-369)**:

* Supports multiple MTPs (MQTT, WebSocket, STOMP)
* Protocol Buffers encoding
* More flexible messaging model

### Documentation

When adding features, update:

1. **Code comments**: Especially for exported functions
2. **GitBook docs** in `docs/`: Add user-facing documentation
3. **README files**: Update relevant README files
4. **API documentation**: If adding new endpoints

### Resources

* **Main Repository**: [github.com/OktopUSP/oktopus](https://github.com/OktopUSP/oktopus)
* **Documentation**: [github.com/OktopUSP/docs](https://github.com/OktopUSP/docs)
* **Community Slack**: Join the [Oktopus Slack](https://join.slack.com/t/oktopustr-369/shared_invite/zt-1znmrbr52-3AXgOlSeQTPQW8_Qhn3C4g)
* **Rest API Spec**: [Oktopus Postman Documentation](https://documenter.getpostman.com/view/18932104/2s93eR3vQY)
* **USP Specification**: Broadband Forum TR-369
* **CWMP Specification**: Broadband Forum TR-069

### Getting Help

* **Community Support**: Available on [Slack](https://join.slack.com/t/oktopustr-369/shared_invite/zt-1znmrbr52-3AXgOlSeQTPQW8_Qhn3C4g)
* **Enterprise Support**: <support@oktopus.app.br>
* **Issues**: Report bugs on [GitHub Issues](https://github.com/OktopUSP/oktopus/issues)

### License

Oktopus is released under the license specified in the `LICENSE` file. Make sure your contributions comply with this license.


# Enterprise Edition

Oktopus empowers ISPs to take control of multi-vendor CPE and IoT fleet to deliver the best customer experience.

Whether you're a regional ISP or a big operator. We have your back covered with the latest technology to unlock the full potential of your devices, delivering the highest level of service quality.

Create an account today, for free: <https://oktopus.app.br/auth/register>.

{% embed url="<https://youtu.be/tYkFt5WT4nI?si=mnhuMC39sWn4xmw0>" %}


# Services

A set of parameters to define the CPE configuration.

The services are used in conjunction with TR-x69 discovery mechanisms to achieve Z**ero Touch Provisioning** flows. The CPE is added to the system beforehand, and the proper service is attached to it. When the field technician plugs the CPE into the network infrastructure, the CPE is provisioned with the parameters defined in the ACS specific to that customer.

{% embed url="<https://youtu.be/AnQlSJmgIGo>" %}

## Service Definition

A service definition is a set of parameters, variables, and operations that dictate the communication structure with the CPE when applying the service to its configuration.

## Scripts

Scripts in the zero-touch-provisioning system are written in **Lua** and provide the provisioning logic for CPE (Customer Premises Equipment) devices. They define how services are applied to devices, including configuration changes, credential management, and parameter updates.

### Execution Flow

1. **Script Initialization**: A Lua script is loaded with the device context (device ID, tenant ID, and variables)
2. **Lua Environment Setup**: The script runs in a secure Lua sandbox with access to predefined global variables and functions
3. **Function Calls**: The script can call Go functions to interact with devices and services
4. **Status Return**: The script must set a `provisioning_status` global variable to indicate success or failure

#### Global Variables

These variables are automatically available to scripts:

| Variable              | Type   | Description                                                              |
| --------------------- | ------ | ------------------------------------------------------------------------ |
| `device_id`           | string | The serial number or unique identifier of the target device              |
| `variables`           | table  | A table containing service-specific variables passed during provisioning |
| `provisioning_status` | string | Must be set to indicate the final status (see below)                     |

**Provisioning Status Values**

| Value           | Meaning                      |
| --------------- | ---------------------------- |
| `"provisioned"` | Service successfully applied |
| `"failed"`      | Service provisioning failed  |

**Important**: Scripts must explicitly set `provisioning_status` before completion. If not set, the provisioning will fail.

#### Example Structure

```lua
-- Access device information
local device_sn = get_device_id()
print("Processing device: " .. device_sn)

-- Access variables
local username = get_variable_value("pppoe_username")
local password = get_variable_value("pppoe_password")

-- Perform operations
local result = send_cwmp_message_and_forget(device_sn, message_xml, timeout)

-- Check result and set status
if result == false or type(result) == "table" then
    set_provisioning_status("failed")
else
    set_provisioning_status("provisioned")
end
```

### Available Functions

#### Provisioning Functions

**`set_provisioning_status(status)`**

Sets the final provisioning status for the script execution.

**Parameters:**

* `status` (string): Either `"provisioned"` or `"failed"`

**Returns:** Nothing

**Example:**

```lua
if success then
    set_provisioning_status("provisioned")
else
    set_provisioning_status("failed")
end
```

***

**`get_device_id()`**

Retrieves the device serial number or identifier currently being provisioned.

**Parameters:** None

**Returns:**

* (string) The device ID

**Example:**

```lua
local sn = get_device_id()
print("Device SN: " .. sn)
```

***

**`get_variable_value(variable_name)`**

Retrieves a variable value from the service instance configuration.

**Parameters:**

* `variable_name` (string): The name of the variable to retrieve

**Returns:**

* (string) The variable value, or nil if not found

**Example:**

```lua
local pppoe_username = get_variable_value("pppoe_username")
local pppoe_password = get_variable_value("pppoe_password")
```

***

#### CPE Interaction Functions

**`send_cwmp_message(device_id, message_body, method, block_queue)`**

Sends a CWMP message to the device and **blocks until the CPE answers** (or the request times out), unlike `send_cwmp_message_and_forget` below, which returns as soon as the message is handed off to the ACS adapter without waiting for — or confirming — the CPE actually applied it. Use this function when the script needs to read back a value or confirm a `Set` actually took effect.

**Parameters:**

* `device_id` (string): The serial number of the target device
* `message_body` (string): XML-formatted CWMP message
* `method` (number): CWMP message type, used to decode the response:
  * `0` = GetParameterValues
  * `1` = SetParameterValues
  * `2` = AddObject
  * `3` = DeleteObject
  * `4` = GetParameterNames
* `block_queue` (boolean, optional): whether to block the device's message queue until this request is answered

**Returns:**

* If the device isn't found, isn't online, or the request fails/times out: a *table* with `error_message` and `error_code`.
* If the CPE answers with a SOAP/CWMP `Fault` (regardless of `method`): a *table* with:
  * `error_message` (string): the top-level `FaultString`
  * `error_code` (string): the top-level `FaultCode` (e.g. `"9003"`)
  * `raw_response` (string): the full raw XML response received from the CPE
  * `parameter_faults` (table, optional): present for `SetParameterValues` faults — an array where each entry has `parameter_name`, `fault_code`, and `fault_string` for the specific parameter that was rejected
* Otherwise, the return shape depends on `method`:
  * `0` (Get): a *table* mapping each requested parameter path to its value.
  * `1` (Set): a *boolean* indicating success.
  * `2` (Add): a *table* with `instance_number` and `status`.
  * `3` (Delete): a *boolean* indicating success.
  * `4` (GetParameterNames): a *table* of parameter names.

**Example:**

```lua
-- Set a parameter, then verify it was actually applied on the CPE
local set_msg = [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:SetParameterValues>
      <ParameterList soapenc:arrayType="cwmp:ParameterValueStruct[1]">
        <ParameterValueStruct>
          <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.Username</Name>
          <Value>]] .. get_variable_value("pppoe_username") .. [[</Value>
        </ParameterValueStruct>
      </ParameterList>
      <ParameterKey></ParameterKey>
    </cwmp:SetParameterValues>
  </soap:Body>
</soap:Envelope>
]]

local set_result = send_cwmp_message(get_device_id(), set_msg, 1)
if type(set_result) == "table" or set_result == false then
    print("Failed to set PPPoE username")
    set_provisioning_status("failed")
    return
end

-- Give the CPE time to apply it, then read it back to confirm
sleep(10)

local get_msg = [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:GetParameterValues>
      <ParameterNames soapenc:arrayType="xsd:string[1]">
        <string>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.Username</string>
      </ParameterNames>
    </cwmp:GetParameterValues>
  </soap:Body>
</soap:Envelope>
]]

local verification = send_cwmp_message(get_device_id(), get_msg, 0)
if verification["error_message"] ~= nil then
    print("Failed to verify PPPoE username: " .. verification["error_message"])
    set_provisioning_status("failed")
    return
end

local param = "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.Username"
if verification[param] == get_variable_value("pppoe_username") then
    print("PPPoE username verified successfully")
    set_provisioning_status("provisioned")
else
    print("PPPoE username verification failed, got: " .. tostring(verification[param]))
    set_provisioning_status("failed")
end
```

***

**`send_cwmp_message_and_forget(device_id, message_body, timeout)`**

Enqueue a CWMP message to the device and does not wait for a response.

**Parameters:**

* `device_id` (string): The serial number of the target device
* `message_body` (string): XML-formatted CWMP message
* `timeout` (number): Timeout in seconds (for the operation)

**Returns:**

* (boolean) `true` if the message was successfully sent
* (table) Error table with `error_message` and `error_code` fields if failed

**Example:**

```lua
local cwmp_msg = [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <cwmp:SetParameterValues>
      <!-- parameters -->
    </cwmp:SetParameterValues>
  </soap:Body>
</soap:Envelope>
]]

local result = send_cwmp_message_and_forget(get_device_id(), cwmp_msg, 2)

if result == false or type(result) == "table" then
    print("Error: " .. result["error_message"] .. " (" .. result["error_code"] .. ")")
    set_provisioning_status("failed")
else
    print("Message sent successfully")
end
```

***

#### Utility Functions

**`sleep(seconds)`**

Pauses script execution for the specified number of seconds.

**Parameters:**

* `seconds` (number): Number of seconds to sleep

**Returns:** Nothing

**Example:**

```lua
print("Waiting for device to process...")
sleep(5)
print("Continuing...")
```

***

**`generate_random_string(length)`**

Generates a random alphanumeric string of the specified length.

**Parameters:**

* `length` (number): Length of the random string to generate (must be > 0)

**Returns:**

* (string) Random string of the specified length, or empty string if length is invalid

**Example:**

```lua
local random_password = generate_random_string(12)
print("Generated password: " .. random_password)
```

***

**`create_or_update_device_credential(username, password)`**

Creates or updates device credentials for authentication.

**Parameters:**

* `username` (string): The username (cannot contain dots, cannot be empty)
* `password` (string): The password (cannot contain dots, cannot be empty)

**Returns:**

* (table) Result table with the following fields:
  * `ok` (boolean): Success flag
  * `username` (string): The full username (prefixed with organization ID)
  * `password` (string): The password
  * `error_msg` (string): Error message if failed, empty if successful

**Validation:**

* Username and password cannot be empty
* Username and password cannot contain dots
* Requires valid organization context

**Example:**

```lua
local result = create_or_update_device_credential("admin_user", "secure_pass_123")

if result["ok"] then
    print("Credential created: " .. result["username"])
    -- Now you can use result["username"] and result["password"] for device login
else
    print("Failed to create credential: " .. result["error_msg"])
    set_provisioning_status("failed")
end
```

***

**`refresh_device_parameters(device_id)`**

Triggers a parameter refresh on the device, forcing it to send an updated parameter list to the ACS.

**Parameters:**

* `device_id` (string): The serial number of the device

**Returns:**

* (boolean) `true` if refresh was successfully triggered, `false` otherwise

**Example:**

```lua
if refresh_device_parameters(get_device_id()) then
    print("Device parameters refresh initiated")
else
    print("Failed to refresh device parameters")
    set_provisioning_status("failed")
end
```

#### Best Practices

1. **Always Check Return Values**: Never assume a function succeeded

   ```lua
   local result = send_cwmp_message_and_forget(device_id, msg, 2)
   if result == false or type(result) == "table" then
       -- Handle error
   end
   ```
2. **Use Descriptive Logging**: Leverage the `print()` function

   ```lua
   print("Step 1: Creating PPPoE WAN connection")
   local result = send_cwmp_message_and_forget(device_id, msg, 2)
   if result == false then
       print("Failed to create PPPoE WAN")
       set_provisioning_status("failed")
       return
   end
   ```
3. **Set Status Explicitly**: Always set provisioning status before script ends

   ```lua
   -- Good
   if success then
       set_provisioning_status("provisioned")
   else
       set_provisioning_status("failed")
   end

   -- Avoid - status not set
   if success then
       print("Success")
   end
   ```
4. **Handle Sequential Operations**: Use proper error handling for multi-step processes

   ```lua
   -- Step 1
   local step1 = send_cwmp_message_and_forget(device_id, msg1, 2)
   if step1 == false then
       set_provisioning_status("failed")
       return
   end

   -- Step 2
   sleep(1)
   local step2 = send_cwmp_message_and_forget(device_id, msg2, 2)
   if step2 == false then
       set_provisioning_status("failed")
       return
   end

   set_provisioning_status("provisioned")
   ```

### Limitations

* Scripts run in a restricted Lua sandbox for security
* File I/O is not available
* Network operations are only available through the provided functions
* Scripts must complete within reasonable timeout limits

### Real-World Example: PPPoE Service Configuration

1. Creates a PPPoE WAN connection on the device
2. Sets PPPoE credentials using variables
3. Changes the WAN type to PPPoE
4. Refreshes device parameters
5. Properly handles errors at each step

The example demonstrates:

* Using variables from the service instance
* Building XML CWMP messages
* Sequential provisioning steps
* Error handling
* Device parameter refresh

```lua
local function createCWMPAddPPPoEWANMsg(wanInstanceNumber)
    return [[
        <?xml version="1.0" encoding="UTF-8"?>
        <soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..schemaswt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <soap:Header/>
        <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
            <cwmp:AddObject>
            <ObjectName>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.]] .. wanInstanceNumber ..[[.WANPPPConnection.</ObjectName>
            <ParameterKey></ParameterKey>
            </cwmp:AddObject>
        </soap:Body>
        </soap:Envelope>
    ]]
end

local function createCWMPSetPPPoECredentialsMsg(wanInstanceNumber, pppoeInstanceNumber)
    return [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..schemaswt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:SetParameterValues>
      <ParameterList soapenc:arrayType="cwmp:ParameterValueStruct[4]">
        <ParameterValueStruct>
            <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.]] .. wanInstanceNumber .. [[.WANPPPConnection.]].. pppoeInstanceNumber ..[[.Enable</Name>
            <Value xsi:type="xsd:boolean">1</Value>
        </ParameterValueStruct>
        <ParameterValueStruct>
            <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.]] .. wanInstanceNumber .. [[.WANPPPConnection.]].. pppoeInstanceNumber ..[[.Username</Name>
            <Value xsi:type="xsd:string">]] .. get_variable_value("pppoe_username") .. [[</Value>
        </ParameterValueStruct>
        <ParameterValueStruct>
            <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.]] .. wanInstanceNumber .. [[.WANPPPConnection.]].. pppoeInstanceNumber ..[[.Password</Name>
            <Value xsi:type="xsd:string">]] .. get_variable_value("pppoe_password") .. [[</Value>
        </ParameterValueStruct>
        <ParameterValueStruct>
            <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.]] .. wanInstanceNumber .. [[.WANPPPConnection.]].. pppoeInstanceNumber ..[[.Name</Name>
            <Value xsi:type="xsd:string">internet-pppoe</Value>
        </ParameterValueStruct>
      </ParameterList>
      <ParameterKey>LC1309946174</ParameterKey>
    </cwmp:SetParameterValues>
  </soap:Body>
</soap:Envelope>
    ]]
end

local function createCWMPChangeWanToPPPoE()
    return [[
        <?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..schemaswt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:SetParameterValues>
      <ParameterList soapenc:arrayType="cwmp:ParameterValueStruct[1]">
        <ParameterValueStruct>
            <Name>InternetGatewayDevice.WANDevice.1.WANCommonInterfaceConfig.X_AVM-DE_InternetConnectionLinkType</Name>
            <Value xsi:type="xsd:string">PPPoE</Value>
        </ParameterValueStruct>
      </ParameterList>
      <ParameterKey>LC1309986174</ParameterKey>
    </cwmp:SetParameterValues>
  </soap:Body>
</soap:Envelope>
    ]]
end 

print("Processing device SN: " .. get_device_id() .. ", adding PPPoE WAN connection")
--add PPPoE WAN Connection
local addPPPoEMsgResult = send_cwmp_message_and_forget(get_device_id(), createCWMPAddPPPoEWANMsg(1), 2)
if type(addPPPoEMsgResult) == "table" or addPPPoEMsgResult == false then
    print("Failed to add PPPoE WAN, SN: " ..
    get_device_id() ..
    " | Error Message: " ..
    addPPPoEMsgResult["error_message"] .. " | Code: " .. addPPPoEMsgResult["error_code"])
    set_provisioning_status("failed")
else

    print("Added PPPoE WAN successfully done, SN: " .. get_device_id() .. ", now setting credentials and changing WAN to PPPoE")
    -- Set PPPoE credentials
    local setPppoeCredentialsMsgResult = send_cwmp_message_and_forget(get_device_id(), createCWMPSetPPPoECredentialsMsg(1, 1), 1)
    
    if type(setPppoeCredentialsMsgResult) == "table" or setPppoeCredentialsMsgResult == false then
        print("Failed to set PPPoE credentials, SN: " .. get_device_id() .. " | Error Message: " .. setPppoeCredentialsMsgResult["error_message"] .. " | Code: " .. setPppoeCredentialsMsgResult["error_code"])
        set_provisioning_status("failed")
    else
        
        print("Set PPPoE credentials successfully done, SN: " .. get_device_id() .. ", now changing WAN to PPPoE")
        -- Change WAN from IPoE to PPPoE
        local changeWanToPPPoEMsgResult = send_cwmp_message_and_forget(get_device_id(), createCWMPChangeWanToPPPoE(), 1)
        
        if type(changeWanToPPPoEMsgResult) == "table" or changeWanToPPPoEMsgResult == false then
            print("Failed to change WAN to PPPoE, SN: " .. get_device_id() .. " | Error Message: " .. changeWanToPPPoEMsgResult["error_message"] .. " | Code: " .. changeWanToPPPoEMsgResult["error_code"])
            set_provisioning_status("failed")
        else
            print("Changed WAN to PPPoE successfully done")
            if refresh_device_parameters(get_device_id()) == false then
                print("Failed to refresh device parameters")
                set_provisioning_status("failed")
            else
                print("Finished processing device SN: " .. get_device_id() .. ", messages queued")
                set_provisioning_status("provisioned")
            end
        end
    end
end
```

## Service Instances

The service instance is the attachment of a service to a CPE, defining the specific variable values for that equipment. It's applied automatically whenever the CPE sends one of the CWMP events configured on its service definition — for example, on first connection or after a factory reset — or on demand at any time, see [Manual Execution](#manual-execution) below.

### Trigger Events

Each service definition declares, via its `events` field, which CWMP events cause Oktopus to automatically apply it to the CPEs attached to it. A definition can list one or more of the standard TR-069 event codes:

| Code | Event                        |
| ---- | ---------------------------- |
| 0    | BOOTSTRAP                    |
| 1    | BOOT                         |
| 2    | PERIODIC                     |
| 3    | SCHEDULED                    |
| 4    | VALUE CHANGE                 |
| 5    | KICKED                       |
| 6    | CONNECTION REQUEST           |
| 7    | TRANSFER COMPLETE            |
| 8    | DIAGNOSTICS COMPLETE         |
| 9    | REQUEST DOWNLOAD             |
| 10   | AUTONOMOUS TRANSFER COMPLETE |

The most common choice is `0 - BOOTSTRAP`, which fires on the CPE's first connection or after a factory reset — this is what makes zero-touch provisioning "zero touch." A definition isn't limited to one event: for example, listing both `0` and `2` re-applies the service both on first contact and whenever the CPE issues a Periodic Inform.

A service definition can also declare **no events at all**. In that case it never runs automatically — it only executes when explicitly triggered, see [Manual Execution](#manual-execution) below.

### Manual Execution

Independent of its configured trigger events (if any), a service instance can always be executed on demand, immediately, without waiting for its trigger event to fire. This is the **only** way a manual-only service definition (one with no `events` configured) is ever executed.

### Status

The service attached to the CPE has a few statuses:

* Pending = It's waiting for a trigger event (or a manual execution) to start running and being applied to the equipment.
* Running = The instance was triggered — by an event or manually — and the script is being executed.
* Provisioned = Execution was a success, the CPE received all the messages.
* Failed = An issue happened during the execution of the script.

## In Practice

After learning the concepts, let's understand how it works in a real use case.

### Configure the CPE to connect to the ACS

The CPE must have a way to connect to the Controller system. You can use one of these options:

* Firmware with ACS pre-configured
* DHCP with option 43
* PPPoE reply with ACS server URL

{% stepper %}
{% step %}

### Add a CPE

Manually insert a new device with its unique identifier (e.g, serial number, agent endpoint id).
{% endstep %}

{% step %}

### Create a service definition

Write the Lua scripts and necessary variables to apply that service to the CPE.
{% endstep %}

{% step %}

### Attach the service to the CPE

Associate the CPE with a service, with its specific variable values for that device.
{% endstep %}

{% step %}

### Plug the CPE into the network

As the CPE already has the ACS configured and a connection to it, all services will be provisioned when it establishes communication with the controller system, and the device will come online with the customer's specific configuration.
{% endstep %}
{% endstepper %}


# Scripts

Automate your workflow with tasks based on events, jobs, schedules, and more. Limitless capabilities with custom resources to fit your needs.

## Introduction

Code is wrritten in **Lua**, which is a lightweight, high-level programming language designed for embedded use, known for its simplicity and efficiency. Created in Brazil in 1993, it features dynamic typing, first-class functions, and a powerful table data structure, making it versatile for various applications. Lua is widely used in game development for scripting game logic, as well as in embedded systems and applications due to its **extensibility and small footprint**, allowing developers to easily integrate it with other languages and platforms.

[LUA 5.1 Manual](https://www.lua.org/manual/5.1/)

### How It Works

To extend Oktopus capabilities and adress the most diverse possible use cases we opted to use a scripting language on top of the actual code, so the [gopher-lua](https://github.com/yuin/gopher-lua) library provides Go APIs that allows to easily embed LUA scripts to Go programs.

The Go software can interact to the LUA script and vice-versa. That way, it's possible to pass functions, parameters and events through both USP Controller/ACS and the user created automations.

### Core Concepts

A "custom function", is as a function called from LUA which translates to a Go function that can interact with NATS, MongoDB, USP Controller, TR-069 ACS and all the other components of the software stack.

<figure><img src="/files/30bplxOxIcHgTFtWWv2n" alt=""><figcaption><p>Diagram of Lua Scripts</p></figcaption></figure>

The "custom functions" will be detailed above in the next topics as just "functions" and separated into domain areas. We hope to provide usefull examples, and the limitation is on each person creativity.

## Functions

### send\_usp\_message()

#### Params:

1. Serial Number \[string]

   The CPE unique identifier.
2. JSON payload \[string]

   Request body to be sent to the CPE.\
   Current supported bodies are :\
   \- Get\
   \- Set

Return:

* If the usp message generates an error:

```json
{
    "error_message": "error to set param test.software",
    "error_code: "500"
}
```

* In the case of a successful transaction each key of the *table* will correspond to one of the parameters requested in the payload.

Example:

{% tabs %}
{% tab title="Get" %}

```lua
local uspMsg = [[
{
    "header": {
        "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0",
        "msg_type": 1
    },
    "body": {
        "request": {
            "get": {
                "paramPaths": [
                    "Device.STOMP."
                ],
                "maxDepth": 2
            }
        }
    }
}
]]

local serial_number = "oktopus-0-stomp"

local uspMessageResult = send_usp_message(serial_number, uspMsg)
if uspMessageResult["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. uspMessageResult["error_message"] .. " | Code: " .. uspMessageResult["error_code"])
else
    for key, value in pairs(uspMessageResult) do
      print(key .. ": " .. value)
    end
end
```

{% endtab %}

{% tab title="Set" %}

```lua
local uspMsg = [[
{
    "header": {
        "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0",
        "msg_type": 4
    },
    "body": {
        "request": {
            "set": {
                "allow_partial":true,
                "update_objs":[
                    {
                        "obj_path":"Device.LocalAgent.",
                        "param_settings":[
                            {
                            "param":"X_VANTIVA-COM_PreConnectTimeout",
                            "value": "55",
                            "required":true
                            }
                        ]
                    }
                ]
            }
        }
    }
}
]]

local serial_number = "oktopus-0-stomp"

local uspMessageResult = send_usp_message(serial_number, uspMsg)
if uspMessageResult["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. uspMessageResult["error_message"] .. " | Code: " .. uspMessageResult["error_code"])
else
    for key, value in pairs(uspMessageResult) do
      print(key .. ": " .. value)
    end
end
```

{% endtab %}

{% tab title="Delete" %}

```lua
local uspMsg = [[
{
    "header": {
        "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0",
        "msg_type": 10
    },
    "body": {
        "request": {
            "delete": {
                "allow_partial": true,
                "obj_paths": [
                    "Device.LocalAgent.Subscription.*."
                ]
            }
        }
    }
}
]]

local serial_number = "oktopus-0-mqtt"

local uspMessageResult = send_usp_message(serial_number, uspMsg)
if uspMessageResult["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. uspMessageResult["error_message"] .. " | Code: " .. uspMessageResult["error_code"])
else
    for key, _ in pairs(uspMessageResult) do
      print(key)
    end
end
```

{% endtab %}

{% tab title="Add" %}

```lua
local uspMsg =[[
{
    "header": {
        "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0",
        "msg_type": 8
    },
    "body": {
        "request": {
            "add": {
                "allow_partial": true,
                "create_objs": [
                    {
                        "obj_path": "Device.LocalAgent.Subscription.",
                        "param_settings": [
                            {
                                "param": "NotifType",
                                "value": "OperationComplete",
                                "required": true
                            },
                            {
                                "param": "Persistent",
                                "value": "1",
                                "required": true
                            },
                            {
                                "param": "ReferenceList",
                                "value": "Device.IP.Diagnostics.DownloadDiagnostics(),Device.IP.Diagnostics.UploadDiagnostics()",
                                "required": true
                            },
                            {
                                "param": "NotifRetry",
                                "value": "0",
                                "required": true
                            },
                            {
                                "param": "Enable",
                                "value": "1",
                                "required": true
                            },
                          	{
                                "param": "Recipient",
                                "value": "Device.LocalAgent.Controller.1",
                                "required": true
                            }
                        ]
                    }
                ]
            }
        }
    }
}
]]

local serial_number = "oktopus-0-mqtt"

local uspMessageResult = send_usp_message(serial_number, uspMsg)
if uspMessageResult["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. uspMessageResult["error_message"] .. " | Code: " .. uspMessageResult["error_code"])
else
    for key, _ in pairs(uspMessageResult) do
      print(key)
    end
end
```

{% endtab %}
{% endtabs %}

### send\_cwmp\_message()

#### Params:

1. Serial Number \[string]

   The CPE unique identifier.
2. XML payload \[string]

   Request body to be sent to the CPE.
3. Message Type \[integer]

   CWMP message type.\
   0 = getParameterValues\
   1 = setParameterValues\
   2 = addObject\
   3 = deleteObject\
   4 = getParameterNames

Return:

The data returned depends on the TR-069 message type:

* If the CWMP message generates an error:

```json
{
    "error_message": "error to set param test.software",
    "error_code: "500"
}
```

* If it is a read or add operation the function will return a *table*.
* If it is a delete or set operation the data returned will be a *boolean* indicating if the result was a success or failure.

Example:

{% tabs %}
{% tab title="GetParameterValues" %}

<pre class="language-lua"><code class="lang-lua">local getParameterValuesType = 0

local xmlContentGet = [[
&#x3C;?xml version="1.0" encoding="UTF-8"?>
&#x3C;soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  &#x3C;soap:Header/>
  &#x3C;soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    &#x3C;cwmp:GetParameterValues>
      &#x3C;ParameterNames>
        &#x3C;string>InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.&#x3C;/string>
       &#x3C;/ParameterNames>
    &#x3C;/cwmp:GetParameterValues>
  &#x3C;/soap:Body>
&#x3C;/soap:Envelope>
]]
local serial_number = "HUAWNFYC-35454645"

<strong>local getTest = send_cwmp_message(serial_number, xmlContentGet, getParameterValuesType)
</strong>if getTest["error_message"] ~= nil then
   print("SN: " .. serial_number .. " | Error Message: " .. getTest["error_message"] .. " | Code: " .. getTest["error_code"])
else
  for key, value in pairs(getTest) do
    print(key .. ": " .. value)
  end
end
</code></pre>

{% endtab %}

{% tab title="AddObject" %}

<pre class="language-lua"><code class="lang-lua">local addObjectType = 2

<strong>local xmlContentAdd = [[
</strong>&#x3C;?xml version="1.0" encoding="UTF-8"?>
&#x3C;soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  &#x3C;soap:Header/>
  &#x3C;soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    &#x3C;cwmp:AddObject>
      &#x3C;ObjectName>InternetGatewayDevice.LANDevice.1.WLANConfiguration.&#x3C;/ObjectName>
      &#x3C;ParameterKey>&#x3C;/ParameterKey>
    &#x3C;/cwmp:AddObject>
  &#x3C;/soap:Body>
&#x3C;/soap:Envelope>
]]
local serial_number = "HUAWNFYC-OPA123-0"

local addTest = send_cwmp_message(serial_number, xmlContentAdd, addObjectType)
if addTest["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. addTest["error_message"] .. " | Code: " .. addTest["error_code"])
 else
    print("instance_number: " .. addTest.instance_number)
    print("status: " .. addTest.status)
end
</code></pre>

{% endtab %}

{% tab title="SetParameterValues" %}

```lua
local setParameterValuesType = 1

local xmlContentSet = [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:SetParameterValues>
      <ParameterList>
        <ParameterValueStruct>
          <Name>InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.Enable</Name>
          <Value>1</Value>
        </ParameterValueStruct>
      </ParameterList>
    </cwmp:SetParameterValues>
  </soap:Body>
</soap:Envelope>
]]
local serial_number = "HUAWNFYC-35454645"

local setTest = send_cwmp_message(serial_number, xmlContentSet, setParameterValuesType)
if type(setTest) == "table" then
   print("SN: " .. serial_number .. " | Error Message: " .. setTest["error_message"] .. " | Code: " .. setTest["error_code"])
else
  print("SetParameterValues worked")
end
```

{% endtab %}

{% tab title="DeleteObject" %}

```lua
local deleteObjectType = 3

local xmlContentDelete = [[
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..\schemas\wt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:DeleteObject>
      <ObjectName>InternetGatewayDevice.LANDevice.1.WLANConfiguration.8.</ObjectName>
      <ParameterKey></ParameterKey>
    </cwmp:DeleteObject>
  </soap:Body>
</soap:Envelope>
]]
local serial_number = "HUAWNFYC-OPA123-0"

local deleteTest = send_cwmp_message(serial_number, xmlContentDelete, deleteObjectType)
if type(deleteTest) == "table" then
    print("SN: " .. serial_number .. " | Error Message: " .. deleteTest["error_message"] .. " | Code: " .. deleteTest["error_code"])
 else
   print("DeleteObject worked")
 end
```

{% endtab %}

{% tab title="GetParameterNames" %}

```lua
local getParameterNamesType = 4

local xmlContentGetParameterNames = [[<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:schemaLocation="urn:dslforum-org:cwmp-1-0 ..schemaswt121.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soap:Header/>
  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <cwmp:GetParameterNames>
      <ParameterPath>InternetGatewayDevice.LANDevice.1.WLANConfiguration.</ParameterPath>
      <NextLevel>1</NextLevel>
    </cwmp:GetParameterNames>
  </soap:Body>
</soap:Envelope>
]]
local serial_number = "HUAWNFYC-OPA123-0"

local getParameternames = send_cwmp_message(serial_number, xmlContentGetParameterNames, getParameterNamesType)
if getParameternames["error_message"] ~= nil then
    print("SN: " .. serial_number .. " | Error Message: " .. getParameternames["error_message"] .. " | Code: " .. getParameternames["error_code"])
 else
  for _, value in pairs(getParameternames) do
    print("Parameter Name" .. value)
  end
end
```

{% endtab %}
{% endtabs %}

### send\_cwmp\_async\_message()

Works the same way as the[ send\_cwmp\_message()](#send_cwmp_message) function, but does not use the CWMP Connection Request mechanism, which means this RPC will only reach the CPE in the next Inform event of it. This allows to reach CPEs behind NAT, although it takes longer for the RPC to reach and be processed by the device.

### listen\_to\_cwmp\_event()

Listen to TR-069 events as defined in the standard.

#### Params:

1. Event \[string] (optional)\
   Possible events:

   ```
   "0"- BOOTSTRAP
   "1"- BOOT
   "2"- PERIODIC
   "3"- SCHEDULED
   "4"- VALUE CHANGE
   "5"- KICKED
   "6"- CONNECTION REQUEST
   "7"- TRANSFER COMPLETE
   "8"- DIAGNOSTICS COMPLETE
   "9"- REQUEST DOWNLOAD
   "10"- AUTONOMOUS TRANSFER COMPLETE
   "11"- DU STATE CHANGE COMPLETE
   "12"- AUTONOMOUS DU STATE CHANGE COMPLETE
   ```

   If no value is set or the value is "\*", than it listens to all events.
2. Serial Number \[string] (optional)\
   CPE unique identifier. If it's not set or the value is "\*", then it listens to all CPEs event(s).
3. Callback function (required)
4. Number of events to process in parallel \[integer] (optional)
5. Discard if processing \[boolean] (optional)\
   When `true`, an incoming event for a device that already has an event being processed (i.e. still running the callback) is discarded instead of waiting for a free worker slot. Useful to avoid piling up stale events for a slow or unresponsive device. Defaults to `false`, which queues the event until a worker slot frees up.

Return:

```json
{
    "serial_number": "HUAWNFYC-35454645",
    "event": "0"
}
```

Example:

```lua
function listen_to_cwmp_event_callback(cwmp_event)
    for key, value in pairs(cwmp_event) do
        print(key .. ": " .. tostring(value))
    end
end
listen_to_cwmp_event("*", "*", listen_to_cwmp_event_callback)
```

To process up to 10 events concurrently, and discard new events arriving for a device that's still being processed:

```lua
function listen_to_cwmp_event_callback(cwmp_event)
    for key, value in pairs(cwmp_event) do
        print(key .. ": " .. tostring(value))
    end
end
listen_to_cwmp_event("*", "*", listen_to_cwmp_event_callback, 10, true)
```

### get\_device()

Get device attributes from its unique identifier.

#### Params:

1. Serial Number \[string]

   The CPE unique identifier.

Return:

If the CPE was not found in the database the function returns a boolean with false value. In case the CPE is found it returns a table with all attributes:

```json
{
    "sn": "HUAWNFYC-35454645",
    "model": "WS7001-40",
    "vendor": "Huawei Technologies Co., Ltd.",
    "version": "",
    "product_class": "Huawei",
    "alias": "",
    "status": 2,
    "cwmp": true,
    "usp": false,
    "data_model": "TR-098"
}
```

`data_model` reflects the CWMP root data model reported by the CPE, either `"TR-098"` (`InternetGatewayDevice.`) or `"TR-181"` (`Device.`); it is empty for USP-only devices.

Example:

```lua
SERIAL_NUMBER = "HUAWNFYC-35454645"
local deviceAttributes = get_device(SERIAL_NUMBER)
if type(deviceAttributes) == "boolean" then
  print("device " .. SERIAL_NUMBER .. " not found")
else
  for key, value in pairs(deviceAttributes) do
    print(key .. ": " .. tostring(value))
  end
end
```

### listen\_to\_new\_device()

Receive all new device attributes that connect to Oktopus, independent of the protocol.

#### Params:

1. Callback function with new device data

```json
{
    "sn": "HUAWNFYC-35454645",
    "model": "WS7001-40",
    "vendor": "Huawei Technologies Co., Ltd.",
    "version": "",
    "product_class": "Huawei",
    "alias": "",
    "status": 2,
    "cwmp": true,
    "usp": false
}
```

Example:

```lua
function listen_to_new_device_callback(new_device)
  for key, value in pairs(new_device) do
    print(key .. ": " .. tostring(value))
  end
end
listen_to_new_device("*", "*", listen_to_new_device_callback)
```

### sleep()

Blocks the code execution for certain time.

#### Params:

1. Time duration \[integer]\
   Seconds of blocking the code execution.

Example:

```lua
while true do
  print("I'm going to appear again after 20 seconds")
  sleep(20)
end
```

### create\_or\_update\_device\_credential()

#### Params:

1. Username \[string]
2. Password \[string]

Return:

*Bool* value indicating *true* if the operation was successfull or *false* if there was an error.

Example:

```lua
local username = "test"
local password = "test"

if create_or_update_device_credential(username, password) then
  print("success")
else
  print("error")
end
```

### generate\_random\_string()

#### Params:

1. Length \[int]\
   Size of the string to be generated

Example:

```lua
local username = "deviceX"
local password = generate_random_string(10)

if create_or_update_device_credential(username, password) then
  print("success")
else
  print("error")
end
```

### delete\_device()

Remove device from database.

#### Params:

1. Serial Number \[string]

   The unique identifier of the CPE to be removed.

Return:

*Bool* value indicating *true* if the operation was successfull and the device was removed from database or *false* if there was an error.

Example:

```lua
local sn = "mqtt_client_92"
if delete_device(sn) then
  print("device " .. sn .. " successfully deleted")
else
  print("error to delete device ".. sn)
end
```

### set\_device\_model()

Update the model attribute of a device in the database.

#### Params:

1. Serial Number \[string]

   The CPE unique identifier.
2. Model \[string]

   The new model value to set.

Return:

*Bool* value indicating *true* if the operation was successfull or *false* if there was an error.

Example:

```lua
local sn = "HUAWNFYC-35454645"
local model = "WS7001-40"

if set_device_model(sn, model) then
  print("device " .. sn .. " model updated to " .. model)
else
  print("error to update device " .. sn .. " model")
end
```

### get\_all\_devices()

Retrieve all devices saved on the database.

Return:

List of device objects.

<pre class="language-json"><code class="lang-json"><strong>[
</strong><strong>    {
</strong>        "sn": "HUAWNFYC-35454645",
        "model": "WS7001-40",
        "vendor": "Huawei Technologies Co., Ltd.",
        "version": "",
        "product_class": "Huawei",
        "alias": "",
        "status": 2,
        "cwmp": true,
        "usp": false
    },
    {
        "sn": "HUAWNFYC-test",
        "model": "WS7001-40",
        "vendor": "Huawei Technologies Co., Ltd.",
        "version": "",
        "product_class": "Huawei",
        "alias": "",
        "status": 2,
        "cwmp": true,
        "usp": false
    },
    ...
]
</code></pre>

Example:

```lua
local devices = get_all_devices()

for i = 1, #devices do
    local device = devices[i]
    print("  SN: " .. device.sn)
end
```

### refresh\_device\_parameters()

Sync device saved parameters, as PPPoE user, IP address and WAN MAC.

Params:

1. Serial number / USP agent endpoint id \[string]\
   The unique identifier of the CPE to be removed.

Return: *Bool* value indicating f the operation was successfully executed ot not.

Example:

```lua
if refresh_device_parameters("sn-test-1") == false then
    print("Failed to refresh device parameters")
else
    print("Device parameters refreshed successfully")
end
```

### http\_request()

Make an HTTP call to a third-party REST API to gather or send data. Useful for integrating scripts with external systems (billing, ticketing, monitoring, etc).

Only `http`/`https` URLs are accepted; URLs with embedded credentials (e.g. `http://user:pass@host/`) are rejected; this function can only reach external/public hosts.

#### Params:

1. Method \[string]

   HTTP method, e.g. `"GET"`, `"POST"`, `"PUT"`, `"PATCH"`, `"DELETE"`. Defaults to `"GET"` if empty.
2. URL \[string]

   Full request URL. Must start with `http://` or `https://`.
3. Headers \[table] (optional)

   Table of header name/value pairs to send with the request.
4. Body \[string] (optional)

   Request body. Commonly used with `POST`/`PUT`/`PATCH`. Use [json\_encode()](#json_encode) to build a JSON body.

Return:

A *table* is always returned:

```json
{
    "ok": true,
    "status_code": 200,
    "body": "{\"plan\": \"gold\"}",
    "headers": {
        "Content-Type": "application/json"
    },
    "error_message": ""
}
```

* `ok` is `true` when the request completed with a `2xx` status code.
* `error_message` is populated when the request could not be completed at all (invalid URL, blocked target, timeout, connection error) — it does not mean the remote API returned an HTTP error.
* Use `body` together with [json\_decode()](#json_decode) to parse JSON responses.

Example:

{% tabs %}
{% tab title="GET" %}

```lua
local response = http_request("GET", "https://api.example.com/customers/12345", {
    ["Authorization"] = "Bearer some-api-token",
    ["Accept"] = "application/json",
}, "")

if response.ok == false then
    print("HTTP request failed | status: " .. tostring(response.status_code) .. " | error: " .. tostring(response.error_message))
    return
end

local data, err = json_decode(response.body)
if err ~= nil then
    print("Failed to decode JSON response: " .. err)
    return
end

print("Customer plan: " .. tostring(data["plan"]))
```

{% endtab %}

{% tab title="POST" %}

```lua
local payload, encErr = json_encode({ status = "active", updated_by = "lua-script" })
if encErr ~= nil then
    print("Failed to encode JSON payload: " .. encErr)
    return
end

local response = http_request("POST", "https://api.example.com/customers/12345", {
    ["Authorization"] = "Bearer some-api-token",
    ["Content-Type"] = "application/json",
}, payload)

if response.ok then
    print("Customer updated successfully")
else
    print("Failed to update customer | status: " .. tostring(response.status_code))
end
```

{% endtab %}
{% endtabs %}

### json\_decode()

Parse a JSON string into a Lua table (or scalar value). Commonly used to read the `body` returned by [http\_request()](#http_request).

#### Params:

1. JSON string \[string]

Return:

Two values are returned: the decoded value (table, string, number, boolean or `nil`), and an error message \[string] that is `nil` when decoding succeeds.

Example:

```lua
local data, err = json_decode('{"sn": "HUAWNFYC-35454645", "online": true}')
if err ~= nil then
    print("Failed to decode JSON: " .. err)
else
    print("sn: " .. data.sn)
end
```

### json\_encode()

Convert a Lua table (or scalar value) into a JSON string. Commonly used to build the `body` sent through [http\_request()](#http_request).

#### Params:

1. Value \[table, string, number or boolean]

Return:

Two values are returned: the encoded JSON \[string], and an error message \[string] that is `nil` when encoding succeeds.

Example:

```lua
local json, err = json_encode({ sn = "HUAWNFYC-35454645", online = true })
if err ~= nil then
    print("Failed to encode JSON: " .. err)
else
    print(json)
end
```

### get\_service\_instances()

List the [service instances](/getting-started/enterprise-edition/services#service-instances) attached to a device.

#### Params:

1. Serial Number \[string]

   The CPE unique identifier.

Return:

A table with `ok` \[boolean], `error_message` \[string], `error_code` \[number] and, on success, `instances` \[table] — an array of tables, each with `id`, `device_id`, `service_id`, `status` and `variables`:

```json
{
    "ok": true,
    "error_message": "",
    "error_code": 200,
    "instances": [
        {
            "id": "665f1b2e8c2a4a1b2c3d4e5f",
            "device_id": "HUAWNFYC-35454645",
            "service_id": "665f1a1e8c2a4a1b2c3d4e5e",
            "status": "provisioned",
            "variables": { "pppoe_username": "customer123" }
        }
    ]
}
```

Example:

```lua
local result = get_service_instances("HUAWNFYC-35454645")
if not result.ok then
    print("Failed to list service instances: " .. result.error_message)
else
    for _, instance in ipairs(result.instances) do
        print(instance.id .. " -> " .. instance.status)
    end
end
```

### create\_service\_instance()

Attach a service definition to a device, creating a new service instance.

#### Params:

1. Serial Number \[string] — the CPE unique identifier.
2. Service Definition ID \[string].
3. Variables \[table, optional] — the service-specific variable values for this device.

Return:

A table with `ok` \[boolean], `error_message` \[string] and `error_code` \[number]. The created instance's id is not returned — call [get\_service\_instances()](#get_service_instances) to look it up.

Example:

```lua
local result = create_service_instance("HUAWNFYC-35454645", "665f1a1e8c2a4a1b2c3d4e5e", {
    pppoe_username = "customer123",
    pppoe_password = "s3cr3t",
})
if not result.ok then
    print("Failed to create service instance: " .. result.error_message)
end
```

### edit\_service\_instance()

Replace an existing service instance's device and variables. The service definition id cannot be changed.

#### Params:

1. Service Instance ID \[string].
2. Serial Number \[string] — the CPE unique identifier.
3. Service Definition ID \[string] — must match the instance's current service definition.
4. Variables \[table, optional] — the new service-specific variable values.

Return:

A table with `ok` \[boolean], `error_message` \[string] and `error_code` \[number].

Example:

```lua
local result = edit_service_instance(
    "665f1b2e8c2a4a1b2c3d4e5f",
    "HUAWNFYC-35454645",
    "665f1a1e8c2a4a1b2c3d4e5e",
    { pppoe_username = "customer123", pppoe_password = "new-password" }
)
if not result.ok then
    print("Failed to edit service instance: " .. result.error_message)
end
```

### delete\_service\_instance()

Delete a service instance.

#### Params:

1. Service Instance ID \[string].

Return:

A table with `ok` \[boolean], `error_message` \[string] and `error_code` \[number].

Example:

```lua
local result = delete_service_instance("665f1b2e8c2a4a1b2c3d4e5f")
if not result.ok then
    print("Failed to delete service instance: " .. result.error_message)
end
```

### apply\_service\_instance()

Run a service instance's provisioning script against its device right away, instead of waiting for its trigger event (see [Manual Execution](/getting-started/enterprise-edition/services#manual-execution)).

#### Params:

1. Service Instance ID \[string].

Return:

A table with `ok` \[boolean], `error_message` \[string], `error_code` \[number] and, on success, `status` \[string] (`"provisioned"` or `"failed"`). `ok` reflects whether the request itself succeeded; `status` is the actual provisioning outcome — a script can legitimately fail provisioning even though the request succeeded (`ok = true`, `status = "failed"`).

Example:

```lua
local result = apply_service_instance("665f1b2e8c2a4a1b2c3d4e5f")
if not result.ok then
    print("Failed to apply service instance: " .. result.error_message)
elseif result.status ~= "provisioned" then
    print("Service instance provisioning failed")
else
    print("Service instance provisioned")
end
```

### apply\_service\_instance\_and\_forget()

Trigger a service instance's provisioning script the same way [apply\_service\_instance()](#apply_service_instance) does, but don't wait for it to run to completion — the request is enqueued and the function returns immediately. Because it doesn't wait for a response, it can't report the final `status`, and `ok` only reflects whether the message was enqueued locally, not whether the zero-touch-provisioning service actually received or ran it.

Use this when applying a service instance whose script may run long (e.g. it calls [sleep()](#sleep) or does several sequential CPE round trips) and the caller doesn't need to wait for the outcome.

#### Params:

1. Service Instance ID \[string].

Return:

A table with `ok` \[boolean], `error_message` \[string] and `error_code` \[number]. There is no `status` field.

Example:

```lua
local result = apply_service_instance_and_forget("665f1b2e8c2a4a1b2c3d4e5f")
if not result.ok then
    print("Failed to enqueue service instance apply: " .. result.error_message)
end
```

<br>


# Profiles

Vendor Lua hooks for devices manager (TR-069 and USP CPEs).

How Lua scripts are executed by Oktopus: [Scripts](/getting-started/enterprise-edition/scripts)

Profiles define the mapping between device-specific data models (TR-069/USP) and the Oktopus common API, enabling QoE analysis and dynamic UI generation.

## Function Contract

All Lua hooks should return two values:

```lua
-- success
return value, nil

-- failure
return nil, "error message"
```

Notes:

* For `get_*` payload builders, return CWMP XML as `string`.
* For `parse_*`, return a Lua table compatible with expected Go resource structs.
* For metadata/capabilities, return string/boolean/table depending on function.

## Shared Helper (optional)

```lua
local function response_to_map(response)
  local out = {}
  for _, param in ipairs(response.list or {}) do
    out[param.name] = param.value
  end
  return out
end
```

## Functions

### get\_vendor()

Returns the vendor label used by the API.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_vendor()
  return "Huawei", nil
end
```

### get\_data\_model()

Returns the data model handled by the script.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (for example: `TR-098`, `TR-181`)
* `err`: `nil` or `string`

Example:

```lua
function get_data_model()
  return "TR-098", nil
end
```

### get\_device\_capabilities()

Defines which resources/features are enabled for this model.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `table` with capability keys
* `err`: `nil` or `string`

Example:

```lua
function get_device_capabilities()
  return {
    Radio = true,
    Ssid = true,
    ConnectedDevices = true,
    Ping = true,
    Traceroute = true,
    Interfaces = true,
    Ports = true,
    Stats = true,
    Hwinfo = true,
    Pon = true,
    Voice = false,
    Cellular = false,
    XDSL = false,
  }, nil
end
```

### get\_device\_specs()

Defines device behavior details (for example traceroute wait time).

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `table`
* `err`: `nil` or `string`

Example:

```lua
function get_device_specs()
  return {
    TimeTraceRoute = 3,
    DirectResult = false,
  }, nil
end
```

### is\_real\_time()

Indicates whether this model should be treated as real-time for operations.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `boolean`
* `err`: `nil` or `string`

Example:

```lua
function is_real_time()
  return false, nil
end
```

### get\_radio()

Builds CWMP payload to fetch radio data.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (CWMP XML)
* `err`: `nil` or `string`

Example:

```lua
function get_radio()
  return cwmp_get_params({
    "InternetGatewayDevice.LANDevice.1.WLANConfiguration.",
  }), nil
end
```

### parse\_radio(response)

Parses CWMP response into radio resource list.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]` CWMP response (`response.list`)

Return:

* `value`: `table` (`[]Radio`)
* `err`: `nil` or `string`

Example:

```lua
function parse_radio(response)
  local m = response_to_map(response)
  local radios = {
    {
      path = "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.",
      enable = { writable = true, value = m["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.RadioEnabled"] == "1" },
      channel = { writable = true, value = tonumber(m["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.Channel"]) or 0 },
    }
  }
  return radios, nil
end
```

### set\_radio(radios)

Builds CWMP payload to update radio configuration.

#### Protocol:

CWMP and USP

#### Params:

1. `radios` `[table]` radio list

Return:

* `value`: `string` (CWMP XML)
* `err`: `nil` or `string`

Example:

```lua
function set_radio(radios)
  -- Return nil to use Go fallback while you implement custom payload.
  return nil, nil
end
```

### get\_ssid()

Builds CWMP payload to fetch SSID data.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (CWMP XML)
* `err`: `nil` or `string`

Example:

```lua
function get_ssid()
  return cwmp_get_params({
    "InternetGatewayDevice.LANDevice.1.WLANConfiguration.",
  }), nil
end
```

### parse\_ssid(response)

Parses CWMP response into SSID resource list.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`[]Ssid`)
* `err`: `nil` or `string`

Example:

```lua
function parse_ssid(response)
  local ssids = {
    {
      path = "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.",
      ssid = { writable = true, value = "MyWiFi" },
      enable = { writable = true, value = true },
    }
  }
  return ssids, nil
end
```

### set\_ssid(ssids)

Builds CWMP payload to update SSID configuration.

#### Protocol:

CWMP and USP

#### Params:

1. `ssids` `[table]`

Return:

* `value`: `string` (CWMP XML)
* `err`: `nil` or `string`

Example:

```lua
function set_ssid(ssids)
  return nil, nil
end
```

### get\_site\_survey\_diagnostic\_state()

Returns site survey diagnostic state.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_site_survey_diagnostic_state()
  return "None", nil
end
```

### set\_site\_survey\_diagnostic\_state()

Sets site survey diagnostic state to Requested.

#### Protocol:

USP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function set_site_survey_diagnostic_state()
  return {
    update_objs = {
      {
        obj_path = "Device.WiFi.NeighboringWiFiDiagnostic.",
        param_settings = {
          { param = "DiagnosticsState", value = "Requested", required = true }
        }
      }
    }
  }, nil
end
```

### get\_site\_survey\_results()

Builds payload to fetch site survey results.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_site_survey_results()
  return cwmp_get_params({
    "InternetGatewayDevice.LANDevice.1.WiFi.NeighboringWiFiDiagnostic.",
  }), nil
end
```

### parse\_get\_site\_survey(response)

Parses site survey result set.

#### Protocol:

CWMP e USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`map[string][]NeighborSites`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_site_survey(response)
  return {
    ["2.4GHz"] = {
      { ssid = "AP-1", channel = 1, signal_strength = -45 },
    }
  }, nil
end
```

### get\_connected\_devices()

Builds payload to fetch connected devices.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_connected_devices()
  return cwmp_get_params({
    "InternetGatewayDevice.LANDevice.1.Hosts.Host.",
  }), nil
end
```

### parse\_get\_connected\_devices(response)

Parses connected devices response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`ConnectedDevices`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_connected_devices(response)
  return {
    ethernet = {
      { host_name = "desktop", ip = "192.168.1.10", mac = "AA:BB:CC:DD:EE:FF" }
    },
    wifi = {}
  }, nil
end
```

### set\_speed\_test(test)

Builds payload to configure speed test.

#### Protocol:

CWMP and USP

#### Params:

1. `test` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_speed_test(test)
  return nil, nil
end
```

### get\_speed\_test\_result(speed\_test\_type)

Builds payload to fetch speed test result.

#### Protocol:

CWMP and USP

#### Params:

1. `speed_test_type` `[string]` (`download` or `upload`)

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_speed_test_result(speed_test_type)
  if speed_test_type == "download" then
    return cwmp_get_params({"InternetGatewayDevice.DownloadDiagnostics."}), nil
  end
  return cwmp_get_params({"InternetGatewayDevice.UploadDiagnostics."}), nil
end
```

### parse\_get\_speed\_test\_result(speed\_test\_type, response)

Parses speed test result.

#### Protocol:

CWMP and USP

#### Params:

1. `speed_test_type` `[string]`
2. `response` `[table]`

Return:

* `value`: `table` (`Speedtest`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_speed_test_result(speed_test_type, response)
  return {
    diagnostic_state = "Completed",
    type = speed_test_type,
    download_speed = "900 Mbps",
    upload_speed = "450 Mbps",
  }, nil
end
```

### get\_download\_diagnostic\_state()

Returns download diagnostic state path or state marker for the model.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_download_diagnostic_state()
  return "InternetGatewayDevice.DownloadDiagnostics.DiagnosticsState", nil
end
```

### get\_upload\_diagnostic\_state()

Returns upload diagnostic state path or state marker for the model.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_upload_diagnostic_state()
  return "InternetGatewayDevice.UploadDiagnostics.DiagnosticsState", nil
end
```

### get\_statistics()

Builds payload to fetch statistics.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_statistics()
  return cwmp_get_params({
    "InternetGatewayDevice.WANDevice.",
    "InternetGatewayDevice.LANDevice.",
  }), nil
end
```

### parse\_get\_statistics(response)

Parses statistics response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`map[string][]Stats`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_statistics(response)
  return {
    wan = {
      { label = "BytesSent", value = "123456" },
      { label = "BytesReceived", value = "987654" },
    },
    lan = {}
  }, nil
end
```

### get\_interface\_wan()

Builds payload to fetch WAN interfaces.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_interface_wan()
  return cwmp_get_params({"InternetGatewayDevice.WANDevice."}), nil
end
```

### get\_interface\_lan()

Builds payload to fetch LAN interfaces.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_interface_lan()
  return cwmp_get_params({"InternetGatewayDevice.LANDevice."}), nil
end
```

### parse\_get\_interface\_wan(response)

Parses WAN interfaces response.

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`map[string]Port`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_interface_wan(response)
  return {
    ["InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1."] = {
      alias = "WAN",
      up = true,
      type = "pppoe",
    }
  }, nil
end
```

### parse\_get\_interface\_lan(response)

Parses LAN interfaces response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`map[string]Port`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_interface_lan(response)
  return {
    ["InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.1."] = {
      alias = "LAN1",
      up = true,
      type = "ethernet",
    }
  }, nil
end
```

### get\_wan\_options()

Returns WAN configuration options supported by the model.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `table` (`[]WanOptions`)
* `err`: `nil` or `string`

Example:

```lua
function get_wan_options()
  return {
    { protocol = "DHCP", vlan = true },
    { protocol = "PPPoE", vlan = true },
  }, nil
end
```

### get\_port()

Builds payload to fetch TR-098/TR-181 ports.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_port()
  return cwmp_get_params({"Device.Ethernet.Interface."}), nil
end
```

### parse\_get\_port(response)

Parses TR-098/TR-181 ports response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`map[string]Port`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_port(response)
  return {
    ["Device.Ethernet.Interface.1."] = {
      alias = "LAN1",
      up = true,
      type = "ethernet",
    }
  }, nil
end
```

### set\_port(ports)

Builds payload to update port configuration.

#### Protocol:

CWMP and USP

#### Params:

1. `ports` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_port(ports)
  return nil, nil
end
```

### add\_port(port)

Builds payload to add a new port/object.

#### Protocol:

CWMP

#### Params:

1. `port` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function add_port(port)
  return nil, nil
end
```

### get\_bridge()

Builds payload to fetch bridge data.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_bridge()
  return cwmp_get_params({"Device.Bridging.Bridge."}), nil
end
```

### parse\_get\_bridge(response)

Parses bridge response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`[]Bridge`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_bridge(response)
  return {
    { path = "Device.Bridging.Bridge.1.", enable = { writable = true, value = true } }
  }, nil
end
```

### set\_bridge(bridges)

Builds payload to update bridge configuration.

#### Protocol:

CWMP and USP

#### Params:

1. `bridges` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_bridge(bridges)
  return nil, nil
end
```

### get\_ping()

Builds payload to fetch ping diagnostics config.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_ping()
  return cwmp_get_params({"InternetGatewayDevice.IPPingDiagnostics."}), nil
end
```

### parse\_get\_ping(response)

Parses ping diagnostics config.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`Ping`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_ping(response)
  return {
    host = { writable = true, value = "8.8.8.8" },
    repetitions = { writable = true, value = 4 },
    timeout = { writable = true, value = 5 },
  }, nil
end
```

### get\_ping\_diagnostic\_state()

Returns ping diagnostic state path/state.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_ping_diagnostic_state()
  return "InternetGatewayDevice.IPPingDiagnostics.DiagnosticsState", nil
end
```

### set\_ping(ping)

Builds payload to execute/update ping diagnostics.

#### Protocol:

CWMP and USP

#### Params:

1. `ping` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_ping(ping)
  return nil, nil
end
```

### get\_ping\_result()

Builds payload to fetch ping result.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_ping_result()
  return cwmp_get_params({"InternetGatewayDevice.IPPingDiagnostics."}), nil
end
```

### parse\_get\_ping\_result(response)

Parses ping result response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`PingResult`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_ping_result(response)
  return {
    diagnostic_state = "Complete",
    success_count = 4,
    failure_count = 0,
    average_response_time = 12,
  }, nil
end
```

### get\_traceroute()

Builds payload to fetch traceroute diagnostics config.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_traceroute()
  return cwmp_get_params({"InternetGatewayDevice.TraceRouteDiagnostics."}), nil
end
```

### parse\_get\_traceroute(response)

Parses traceroute diagnostics config.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`Traceroute`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_traceroute(response)
  return {
    host = { writable = true, value = "8.8.8.8" },
    max_hop_count = { writable = true, value = 30 },
    timeout = { writable = true, value = 5 },
  }, nil
end
```

### get\_traceroute\_diagnostic\_state()

Returns traceroute diagnostic state path/state.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_traceroute_diagnostic_state()
  return "InternetGatewayDevice.TraceRouteDiagnostics.DiagnosticsState", nil
end
```

### set\_traceroute(traceroute)

Builds payload to execute/update traceroute diagnostics.

#### Protocol:

CWMP and USP

#### Params:

1. `traceroute` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_traceroute(traceroute)
  return nil, nil
end
```

### get\_traceroute\_result\_number\_of\_hops()

Returns the parameter path/state used to read hop count.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string`
* `err`: `nil` or `string`

Example:

```lua
function get_traceroute_result_number_of_hops()
  return "InternetGatewayDevice.TraceRouteDiagnostics.RouteHopsNumberOfEntries", nil
end
```

### get\_traceroute\_result(number\_of\_hops)

Builds payload to fetch traceroute hop details.

#### Protocol:

CWMP and USP

#### Params:

1. `number_of_hops` `[integer]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_traceroute_result(number_of_hops)
  return cwmp_get_params({"InternetGatewayDevice.TraceRouteDiagnostics.RouteHops."}), nil
end
```

### parse\_get\_traceroute\_result(response)

Parses traceroute result response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`TracerouteResult`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_traceroute_result(response)
  return {
    diagnostic_state = "Complete",
    hops = {
      { hop_host = "192.168.1.1", hop_error_code = 0, hop_rtt_times = { 1, 1, 2 } },
      { hop_host = "8.8.8.8", hop_error_code = 0, hop_rtt_times = { 10, 11, 12 } },
    }
  }, nil
end
```

### get\_hwinfo()

Builds payload to fetch hardware information.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_hwinfo()
  return cwmp_get_params({
    "InternetGatewayDevice.DeviceInfo.",
  }), nil
end
```

### parse\_get\_hwinfo(response)

Parses hardware information response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`Hwinfo`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_hwinfo(response)
  return {
    manufacturer = "Huawei",
    model_name = "WS7001-40",
    serial_number = sn,
    software_version = "1.0.0",
  }, nil
end
```

### get\_pon()

Builds payload to fetch PON information.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_pon()
  return cwmp_get_params({
    "InternetGatewayDevice.WANDevice.1.X_GponInterafceConfig.",
  }), nil
end
```

### parse\_get\_pon(response)

Parses PON information response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`Pon`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_pon(response)
  return {
    status = "Up",
    rx_power = "-19.2 dBm",
    tx_power = "2.1 dBm",
  }, nil
end
```

### get\_voice()

Builds payload to fetch voice accounts/lines.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_voice()
  return cwmp_get_params({"InternetGatewayDevice.Services.VoiceService."}), nil
end
```

### parse\_get\_voice(response)

Parses voice data response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`[]Voice`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_voice(response)
  return {
    {
      profile = "SIP",
      lines = {
        { number = "1001", enabled = true }
      }
    }
  }, nil
end
```

### set\_voice(voice)

Builds payload to update voice profile settings.

#### Protocol:

CWMP

#### Params:

1. `voice` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_voice(voice)
  return nil, nil
end
```

### set\_line(line)

Builds payload to update voice line settings.

#### Protocol:

CWMP

#### Params:

1. `line` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_line(line)
  return nil, nil
end
```

### get\_cellular()

Builds payload to fetch cellular information.

#### Protocol:

CWMP and USP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_cellular()
  return cwmp_get_params({"Device.Cellular.Interface."}), nil
end
```

### parse\_get\_cellular(response)

Parses cellular information response.

#### Protocol:

CWMP and USP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`[]Cellular`)
* `err`: `nil` or `string`

Example:

```lua
function parse_get_cellular(response)
  return {
    {
      interface = "wwan0",
      status = "Up",
      signal = "-75 dBm",
    }
  }, nil
end
```

### set\_cellular(cellular)

Builds payload to update cellular settings.

#### Protocol:

CWMP and USP

#### Params:

1. `cellular` `[table]`

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function set_cellular(cellular)
  return nil, nil
end
```

### get\_xdsl()

Builds payload to fetch xDSL information.

#### Protocol:

CWMP

#### Params:

None.

Return:

* `value`: `string` (XML)
* `err`: `nil` or `string`

Example:

```lua
function get_xdsl()
  return cwmp_get_params({"InternetGatewayDevice.WANDevice.1.WANDSLInterfaceConfig."}), nil
end
```

### parse\_xdsl(response)

Parses xDSL information response.

#### Protocol:

CWMP

#### Params:

1. `response` `[table]`

Return:

* `value`: `table` (`[]XDSL`)
* `err`: `nil` or `string`

Example:

```lua
function parse_xdsl(response)
  return {
    {
      status = "Up",
      downstream_rate = "90 Mbps",
      upstream_rate = "18 Mbps",
    }
  }, nil
end
```


# MCP Server

Provide AI assistants direct access to Oktopus's device management capabilities and data.

Instead of users manually checking dashboards or copying device information, an MCP server lets AI tools query device status, retrieve configurations, execute commands, diagnose/troubleshoot issues, and apply solutions, all within a natural conversation.

### Why is it so powerful?

Support teams can leverage AI to diagnose issues, gather logs, suggest fixes, and analyse the network to execute device commands — reboots, firmware updates, and configuration changes — on behalf of users, significantly reducing the technical barrier to issue resolution.

AI assistants can pull live device data, network diagnostics, and performance metrics, then provide immediate insights and recommendations based on your current system state. Rather than working with stale dashboards or historical reports, the AI responds to what's happening in your network right now, enabling faster decision-making and proactive problem-solving.

You can integrate Oktopus, CRM, billing, and other systems with the MCP servers to talk to the LLM in natural language, providing it with the full context of the ISP's operational stack. It's like talking to an employee who has full knowledge of the company's operation, including sales, engineering, marketing, finances, etc.

#### Practical Use Cases

* **Network Troubleshooting**: "Why is Device XYZ offline?" → AI queries device status, pulls diagnostics, retrieves logs, and suggests fixes in seconds.
* **Bulk Device Management**: "Update firmware on all devices in Building 5" → AI queries device list, checks compatibility, and executes updates autonomously.
* **Support Automation**: Support agents describe a problem → AI gathers device diagnostics, interface stats, and configuration details to pinpoint issues faster.
* **Performance Monitoring**: "Show me devices with connectivity issues" → AI retrieves live metrics and alerts, then recommends corrective actions.
* **Configuration Audits**: "Are all devices using NTP?" → AI queries device configurations across the fleet and reports non-compliance.
* **Incident Response**: When an alert fires, AI automatically pulls affected device data, checks related devices, and escalates with full context.

## Quick Start

How to interact with Oktopus components using an LLM like Claude, ChatGPT, Gemini, or Ollama.

The MCP server acts as a bridge between an LLM (Large Language Model) and your Oktopus infrastructure. When you ask the LLM a question about your devices, it recognizes that it needs information from Oktopus and uses MCP to request that data. The LLM then processes this real-time information and provides you with intelligent, context-aware responses and recommendations.

Popular LLM options that support MCP include OpenAI's ChatGPT, Claude (from Anthropic), Google's Gemini, and open-source models like Llama. The specific LLM you choose is up to you.

### Claude

{% stepper %}
{% step %}

#### Go to Customize Tab

<figure><img src="/files/aCe5dVmT4iyRupfmliDF" alt=""><figcaption><p>Claude Customze Tab</p></figcaption></figure>
{% endstep %}

{% step %}

#### Add Custom Connector

<figure><img src="/files/UFPRuP1kxkqgjIpppzmz" alt=""><figcaption><p>Claude Add Custom Connector</p></figcaption></figure>
{% endstep %}

{% step %}

#### Add Oktopus MCP Server URL

The server domain can vary based on your account and deployment type. As a rule, utilize "https\://\<oktopus-server-domain>/mcp".

<figure><img src="/files/KqleVuG9LG8chVulgvR8" alt=""><figcaption><p>Add Oktopus MCP Server URL at Claude</p></figcaption></figure>
{% endstep %}

{% step %}

#### Connect

<figure><img src="/files/Ypxl4xTSZ9BAG7Ol09eC" alt=""><figcaption><p>Apply Oktopus Connection at Claude</p></figcaption></figure>
{% endstep %}

{% step %}

#### Authenticate & Authorize

<figure><img src="/files/bRQZV2sVqut0XSxlEnbi" alt=""><figcaption><p>Authenticate to the MCP server with user and password</p></figcaption></figure>

<figure><img src="/files/GlowgfMrf68jjQ71H1te" alt=""><figcaption><p>Authorize LLM access to your Oktopus account</p></figcaption></figure>
{% endstep %}
{% endstepper %}

### ChatGPT

As of this writing, custom MCP servers are not available in the free plan.

### Local LLMs

Several open-source and self-hosted LLM options are available for organizations prioritizing data privacy and local control. Check [Local vs 3rd Party LLM](#local-vs-3rd-party-llm) for a comparison.

To integrate a local LLM with Oktopus MCP, you run the LLM on your infrastructure (either on-premises or in a private cloud environment) and connect it to the MCP server using either the stdio protocol for direct process-based communication or the HTTP protocol if you're running the LLM as a separate service. With stdio, the MCP server and LLM run as connected processes on the same machine, providing low-latency communication. With HTTP, you expose the LLM as a REST API endpoint (typically on localhost or an internal network) that the MCP server can call remotely.

## Deployment Options

The Oktopus MCP server can be deployed using two different communication protocols.

We recommend using the MCP communication with HTTP transport for convenience. The tutorial and documentation materials focus on this *modus operandi.*

### Stdio

The MCP server communicates via standard input/output streams. This is a direct, process-based connection ideal for local or tightly integrated environments. You can download the Oktopus MCP server binary at: <https://github.com/OktopUSP/mcp-server-public>.

### **HTTP**

The MCP server exposes a REST API endpoint that can be accessed over HTTP. This enables network-based communication and allows remote clients to connect to the server from anywhere.

### Local vs 3rd Party LLM

Running a local LLM keeps all processing and data within your infrastructure, providing maximum privacy and control, no data leaves your network, and you're not dependent on external service availability or pricing. However, local LLMs typically require significant computational resources to run efficiently + engineering overhead for deployment and maintenance, while very probably having lower accuracy or capabilities compared to state-of-the-art models.

Third-party LLMs (like OpenAI's GPT-4, Claude, or Google's Gemini) are hosted in the cloud and require sending your queries and device data to external servers, which raises privacy and compliance considerations depending on your regulations. The tradeoff is that third-party LLMs generally offer superior performance, more advanced capabilities, and require minimal infrastructure investment on your end.

Choose local LLMs if data residency and privacy are critical; choose third-party LLMs if you prioritize cutting-edge performance and ease of deployment.


# Integrations

Alright, you've just set up Oktopus to manage all your multi-vendor devices, hoping it to be a one platform, with one UI to solve all your problems, but then suddenly you realize that you have other systems and software for different purposes, but you wish all those solutions to converge into a cohesive ecosystem.

For this matter, Oktopus provides some alternatives:

### REST API

To integrate Oktopus into your ecosystem, you can rely on [Oktopus Controller REST API](https://documenter.getpostman.com/view/18932104/2s93eR3vQY). It's documented in Postman, with many usage examples in different programming languages, and lots of possibilities.

{% embed url="<https://youtu.be/HXxYKSMdx_E?si=8IXs9Np5tQTGJ3HQ>" %}

### NATS

For advanced users who may need real-time information and have experience with PUB/SUB messaging concepts, it's also possible to use NATS.

### MCP Server

Available in the Commercial Edition, it is a standardized way to connect AI assistants to your application, allowing intelligent systems to interact seamlessly with your data and functionality. Unlike direct REST API calls or direct messaging connections, [Oktopus MCP server](/getting-started/enterprise-edition/mcp-server) acts as an intelligent intermediary that exposes your application's features as discoverable "tools" that AI models can understand and use naturally within conversations.


# Simulators

The device simulators are designed to replicate real-world scenarios of device configuration by adhering to the Broadband Forum (BBF) standards. Providing a virtual environment where users can simulate various settings and behaviors, allowing for comprehensive testing and validation of BBF- standards compliant ecosystem software. By mimicking real-world conditions, the simulators helps to ensure that the "controllers" meet industry standards, facilitating interoperability and enhancing overall network performance.


# (USP) Agent Simulator

Simulate an USP agent, based on OBUSPA.

Agent is a piece of software that runs on the CPE or IoT device and is responsible for connecting it to the Controller (Oktopus) through USP. To simulate the behavior of a real device, we can use [OBUSPA](/usp-tr-369/architecture#obuspa-open-broadband-user-services-platfrom-agent) encapsulated in a docker container for better portability.

OBUSPA-based agent simulator utility packaged by Oktopus depends on initial configuration files available in the[/agent](https://github.com/OktopUSP/oktopus/tree/main/agent) folder. You can edit those files to simulate more complex environments. Run the commands below inside[ ](https://github.com/OktopUSP/oktopus/tree/main/agent)[root folder](https://github.com/OktopUSP/oktopus/tree/main). Each tab shows how to connect to a different MTP:

{% tabs %}
{% tab title="MQTT" %}

```bash
docker run -d -v $(pwd)/agent/oktopus-mqtt-obuspa.txt:/obuspa/oktopus-mqtt-obuspa.txt --network host --name obuspa-mqtt oktopusp/obuspa:latest obuspa -r /obuspa/oktopus-mqtt-obuspa.txt -p -v4 -i lo
```

{% endtab %}

{% tab title="Websockets" %}

```bash
docker run -d -v $(pwd)/agent/oktopus-websockets-obuspa.txt:/obuspa/oktopus-websockets-obuspa.txt --network host --name obuspa-websockets oktopusp/obuspa:latest obuspa -r /obuspa/oktopus-websockets-obuspa.txt -p -v4 -i lo
```

{% endtab %}

{% tab title="STOMP" %}

```bash
docker run -d -v $(pwd)/agent/oktopus-stomp-obuspa.txt:/obuspa/oktopus-stomp-obuspa.txt --network host --name obuspa-stomp oktopusp/obuspa:latest obuspa -r /obuspa/oktopus-stomp-obuspa.txt -p -v4 -i lo
```

{% endtab %}
{% endtabs %}

### Flags:

```
--help (-h)       Displays this help
--log (-l)        Sets the destination for debug logging. Default is 'stdout'. Can also use 'syslog' or a filename
--dbfile (-f)     Sets the path of the file to store the database in (default=/usr/local/var/obuspa/usp.db)
--verbose (-v)    Sets the debug verbosity log level: 0=Off, 1=Error(default), 2=Warning, 3=Info
--prototrace (-p) Enables trace logging of the USP protocol messages
--cli (-s)        Sets the path of the Unix domain socket file used for CLI communications
--authcert (-a)   Sets the path of the PEM formatted file containing a client certificate and private key to authenticate this device with
--truststore (-t) Sets the path of the PEM formatted file containing trust store certificates
--resetfile (-r)  Sets the path of the text file containing factory reset parameters
--interface (-i)  Sets the name of the networking interface to use for USP communication
--meminfo (-m)    Collects and prints information useful to debugging memory leaks
--register (-R)   Sets the top-level data model objects to register when acting as a USP Service
--error (-e)      Enables printing of the callstack whenever an error is detected
--command (-c)    Sends a CLI command to the running USP Agent and prints the response
                  To get a list of all CLI commands use '-c help'
--plugin (-x)     Specifies the path to a shared object vendor layer plug-in
```


# Open Source Platforms

With transparency, flexibility, and collaborative nature, open-source router platforms are the ideal choice for businesses aiming to deliver innovative, secure, and scalable networking solutions.

Adopting open-source platforms like RDK-B, PrplOS, and OpenWRT ensures future-proof solutions that can adapt to evolving technologies. These platforms empower businesses to maintain full control over software functionality, updates, and security while being built upon rock-solid standardized foundations.

By leveraging global standards and community support, you gain access to a vast pool of documentation and expertise, enabling quicker problem resolution and feature implementation. Open-source platforms provide the pillars for creating differentiated products, allowing companies to stand out in competitive markets.

## Benefits

Significant cost savings by removing the need for expensive licensing fees. They allow businesses to avoid being locked into proprietary ecosystems, which can lead to substantial long-term financial advantages.

### Flexibility

Solutions can be tailored to specific use cases, enabling developers to customize features, interfaces, and protocols to align with their goals. This level of adaptability ensures that the software remains highly relevant and functional, no matter the deployment scenario.

### Community-Driven

Open-source projects thrive on the contributions of a global developer community. This collaborative model leads to rapid development cycles, regular feature updates, and quick adoption of emerging technologies. As a result, users benefit from cutting-edge solutions that evolve continuously.

### Security

As the source code is accessible to anyone, vulnerabilities are quickly identified and addressed. This transparency fosters a more secure environment compared to closed systems, where vulnerabilities might remain hidden for extended periods.

### Interoperability

Adhere open standards, making it easier to integrate solutions with a wide range of devices and networks. This compatibility simplifies the deployment process and reduces troubleshooting efforts.

### Go-To-Market

The development and deployment process is also accelerated when using open-source platforms. Pre-existing, well-maintained components can be readily implemented, allowing developers to focus on building unique features rather than reinventing foundational aspects of the system.


# PrplOS

## Introduction

PrplOS is an open-source operating system designed specifically for embedded devices and networks within the home broadband ecosystem. It is part of the prpl Foundation's initiative to support an open, distributed architecture for service delivery across the digital home. To know more access: <https://prplfoundation.org/>

{% hint style="info" %}
This tutorial was tested against [PrplOS release version 3.1.0](https://gitlab.com/prpl-foundation/prplos/prplos/-/releases/prplos-v3.1.0)
{% endhint %}

## Advantages

* **Interoperability**: PrplOS provides compatibility with a wide range of devices and platforms, fostering a multi-vendor environment.
* **Security**: Built with security as a priority, PrplOS offers features such as secure boot, firmware updates, and sandboxing to protect devices from potential threats.
* **Flexibility and Modularity**: Its modular architecture allows for customization, enabling developers to tailor the OS to specific device requirements and functionalities.
* **Efficiency**: Optimized for low-power and resource-constrained devices, PrplOS ensures efficient performance suited for various IoT applications.
* **Community-Driven**: As an open-source project, it benefits from community contributions, enhancing its features and ensuring rapid adaptation to new technologies.

## Installation

Can be installed on supported hardware platforms using standard interfaces like JTAG or UART. Prebuilt binaries are available for various development boards, allowing a straightforward flashing process with tools such as OpenOCD or manufacturer-specific utilities. This ensures quick deployment and reduces the need for extensive configuration.

For testing and debugging, simulation environments provide an alternative to physical devices. Using emulators like QEMU, developers can run the system in a virtual setup, enabling safe experimentation and troubleshooting. These options make installation adaptable to both hardware deployment and software development workflows.

### Quick Start

The easiest and fastest way to get everything running is to download a pre-built image from PrplOS repository releases and deploy it on your target platform. It's very straightforward to transform the image in a format to be used by your prefered hypervisor and launch a VM with Prpl as the operating system.

You get have some knowledge with linux networking and CLI, it's important for setting up the operating system network configuration to reach the outside world and to be able to access the guest host through SSH for convenience.\
\
Check more installation options and target platforms at: <https://gitlab.com/prpl-foundation/prplos/prplos/-/wikis/home>.

## Configuration

### TR-369 / USP

First, setup a command alias to facilitate the next steps.

```shell
alias usp="obuspa -f /etc/obuspa.db -c"
```

Setup new USP Controller Endpoint Id:

```shell
usp set Device.LocalAgent.Controller.1.EndpointID oktopusController
```

> "Essentially, *obuspa* waits for up to 10 minutes for the presence of the *Device.DeviceInfo* and *Device.Time.Status* data model entries, ensuring that *Device.Time.Status* is in a *Synchronized* state.
>
> This timeout is configurable via Device.LocalAgent.X\_VANTIVA-COM\_PreConnectTimeout (value in seconds, 0 = disable) and can be modified either in the default *obuspa* configuration file located at [/etc/config/obuspa\_param\_reset.txt](https://gitlab.com/prpl-foundation/prplos/feeds/feed_usp/-/blob/main/apps/obuspa/files/etc/config/obuspa_param_reset.txt?ref_type=heads#L59) or at runtime \[...]"

```sh
usp set Device.LocalAgent.X\_VANTIVA-COM\_PreConnectTimeout 60
```

```shell
service obuspa restart
```

In the below commands, exchange "\<oktopus-controller-host>" for the corresponding value of your scenario.

{% tabs %}
{% tab title="MQTTv5" %}
Define the Controller MTP as MQTT:

```shell
usp set Device.LocalAgent.Controller.1.MTP.1.Protocol MQTT
```

```shell
usp set Device.LocalAgent.MTP.1.Protocol MQTT
```

Create new MQTT client:

```shell
usp add Device.MQTT.Client
```

Setup MQTT client broker address:

```shell
usp set Device.MQTT.Client.1.BrokerAddress <oktopus-controller-host>
```

Associate Local Agent with MQTT Client:

<pre class="language-shell"><code class="lang-shell"><strong>usp set Device.LocalAgent.MTP.1.MQTT.Reference Device.MQTT.Client.1
</strong></code></pre>

Associate USP Controller with MQTT Client:

```shell
usp set Device.LocalAgent.Controller.1.MTP.1.MQTT.Reference Device.MQTT.Client.1
```

Configure MQTT topics:

```shell
usp set Device.LocalAgent.MTP.1.MQTT.ResponseTopicConfigured oktopus/usp/v1/controller
```

```shell
usp set Device.LocalAgent.Controller.1.MTP.1.MQTT.Topic oktopus/usp/v1/agent
```

Enable MQTT client connection:

```shell
usp set Device.MQTT.Client.1.Enable true
```

{% endtab %}

{% tab title="STOMP" %}
Define the Controller MTP as STOMP:

```shell
usp set Device.LocalAgent.Controller.1.MTP.1.Protocol STOMP
```

```shell
usp set Device.LocalAgent.Controller.1.MTP.1.Protocol STOMP
```

Setup the STOMP instance connection:

```shell
usp set Device.STOMP.Connection.1.Host <oktopus-controller-host>
```

{% endtab %}

{% tab title="Websockets" %}

```shell
// Tutorial coming soon ...
```

{% endtab %}
{% endtabs %}

***

### Device Logs

```shell
logread -f
```

or

```shell
logread -f | grep -i obuspa
```

### Clean all

In case you get stuck with an issue or misconfiguration, clean up your environment.

{% hint style="info" %}
**CAUTION**: This action will factory reset all the TR-181 datamodel.
{% endhint %}

```shell
rm /etc/obuspa.db &&
service obuspa restart
```


# OpenWRT

The OpenWrt Project is a Linux operating system targeting embedded devices. Instead of trying to create a single, static firmware, OpenWrt provides a fully writable filesystem with package management. This frees you from the application selection and configuration provided by the vendor and allows you to customize the device through the use of packages to suit any application. For developers, OpenWrt is the framework to build an application without having to build a complete firmware around it; for users this means the ability for full customization, to use the device in ways never envisioned.

For more details go to the OpenWRT oficial Website at: [https://openwrt.org/](https://easycwmp.org/get/)

## Installation

TR069 client is not available by default in OpenWRT images. Most users use [EasyCwmp](https://easycwmp.org/) to have CWMP management capabilities. It's possible to build easycwmp package and install in your OpenWRT system or compile the entire system with EasyCwmp enabled.

### Build Package

Download the OpenWRT SDK. It's available in the same page as OpenWRT images. After that, download MicroXML and EasyCwmp and put them under the packages folder from the SDK:

* [EasyCwmp download](https://easycwmp.org/get/) :arrow\_down:
* [MicroXML download](https://easycwmp.org/download/libmicroxml.tar.gz) :arrow\_down:

```shell
cd openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64
┌[user@linux] [/dev/pts/2]
└[~/Documents/GitHub/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64]> cd package
┌[user@linux] [/dev/pts/2]
└[~/Documents/GitHub/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64/package]> ls
easycwmp  kernel  libmicroxml  Makefile  toolchain
┌[user@linux] [/dev/pts/2]
└[~/Documents/GitHub/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64/package]>
```

Build both packages:

```shell
make menuconfig
```

Navigate to libmicroxml and easycwmp and check both of them.

```shell
make package/easycwmp/compile
```

After compilation is finished both packages will be available at the folder **bin/packages/x86\_64/base/**

Copy both files to inside the OpenWRT system and install them using the apk utility:

```shell
root@OpenWrt:~# apk add libmicroxml-1.0.0-r1.apk
fetch https://downloads.openwrt.org/snapshots/targets/x86/64/packages/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/base/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/luci/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/packages/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/routing/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/telephony/packages.adb
fetch https://downloads.openwrt.org/snapshots/packages/x86_64/video/packages.adb
OK: 16 MiB in 131 packages
```

```shell
root@OpenWrt:~# apk add easycwmp-1.8.6.apk
OK: 16 MiB in 131 packages
```

Check if EasyCwmp is working:

```shell
root@OpenWrt:~# /etc/init.d/easycwmpd start
root@OpenWrt:~# /etc/init.d/easycwmpd status
running
root@OpenWrt:~# easycwmp get Device.DeviceInfo.Manufacturer
{ "parameter": "Device.DeviceInfo.Manufacturer", "value": "OpenWrt\nhttps___openwrt.org_" }
```

## Configuration

To configure EasyCwmp parameter edit the file **/etc/config/easycwmp**

You can set the bellow settings:

```
config local
	option enable '1'
	option interface 'eth0'
	option port '7547'
	option ubus_socket '/var/run/ubus.sock'
	option date_format '%FT%T%z'
	option username 'easycwmp'
	option password 'easycwmp'
	option authentication 'Digest'
	option logging_level '3'

config acs
	option url 'http://192.168.10.53:9292/acs'
	option username 'easycwmp'
	option password 'easycwmp'
	option periodic_enable '1'
	option periodic_interval '100'
	option periodic_time '0001-01-01T00:00:00Z'

config device
	option manufacturer 'OpenWrt
https___openwrt.org_'
	option oui 'FFFFFF'
	option product_class 'Generic'
	option serial_number 'FFFFFF123456'
	option hardware_version 'v0'
	option software_version 'r28605-9decfc82dc'
```

Change the option **url** and point it to the Oktopus controller. Restart the EasyCwmp daemon and check if the device appears at the Oktopus Controller Devices tab:

```shell
vi /etc/config/easycwmp
/etc/init.d/easycwmpd restart
```

Controller View:

<figure><img src="/files/BrLUuCFQihQwfxkPYGue" alt=""><figcaption><p>OpenWRT Device Online</p></figcaption></figure>

Author: [Leonardo Henrique Meres](https://www.linkedin.com/in/leonardo-henrique-meres-silva-28241b2b/)


# Introduction

USP (TR-369) is the evolution of the TR-069 standard, providing superior network performance, security, virtualized applications, and device scaling in multi-vendor, IoT-ready connected home.

> USP (TR-369) is the evolution of the TR-069 standard, providing superior network performance, security, virtualized applications, and device scaling in multi-vendor, IoT-ready connected home environments.

The **User Services Platform** is a standardized **protocol for managing, monitoring, upgrading, and controlling connected devices**. USP allows service providers, consumer electronics manufacturers, and end users to:

* Create interoperable and vendor-independent **managed Wi-Fi systems**.
* Perform **lifecycle management** of consumer connected devices and containerized microservices.
* Support **independent applications** for application-enabled services gateways.
* Enable **IoT and consumer electronics upgradability** for critical security patches.
* Develop applications that gather the telemetry necessary to **mass data processing, AI, and machine learning**.
* **Bootstrap and configure** newly installed or purchased devices and applications.
* Let customer support **monitor and troubleshoot** connected devices, services, and home network links.
* Easily map the home network to **control service quality and monitor threats**.
* Securely **control IoT, smart home, and smart networking functions** locally or from the Cloud.
* **Enable multi-tenant** (i.e., multi-stakeholder) management and control.

USP represents the natural evolution of the Broadband Forum’s [CPE WAN Management Protocol (CWMP)](http://www.broadband-forum.org/cwmp), commonly known as TR-069. It uses an expanded version of the Device:2 Data Model to represent device operations (firmware upgrades, reboots, etc.), network interfaces, and service functions (IoT functions, VoIP, etc.).

## CWMP Evolution to USP

The advent of the Internet of Things brings countless opportunities and challenges for service providers, with over a billion devices across the globe today making use of [TR-069](https://www.broadband-forum.org/download%20/TR-069_Amendment-2.pdf), what is the future of the protocol and what can we expect ahead?

The CWMP (CPE Wan Management Protocol), better known as TR-069, opened many doors for the ecosystem of providers, through which it is possible to deliver services with agility, which meet or exceed customer expectations, with proactive management and secure network, also bearing in mind the lower cost and greater efficiency for service providers.

With the rise of what we now call the smart home, the Internet of Things and the demand for increasingly interconnected and cloud-based environments, new demands and obstacles have emerged, opening the door to the creation of a new form of communication that meets the needs of current market needs.

There is a fierce race to monetize the IoT devices that are now part of the connected home and other environments. As a result, many companies are creating their own proprietary solutions; this is understandable given such pressure generated by the promise of Smart Home monetization. Unfortunately, these applications contribute to a poor ecosystem, where a provider ends up dependent and limited to a vertical solution, from a single vendor. This generates a **low competition environment (which leads to greater risks), less innovation, and the potential for very high cost solutions.**

The technologies behind Wi-Fi, device-to-device connectivity, the Smart Home and IoTs are constantly evolving and improving. It is important that when service providers look for a solution, they look for something that is "future proof", always thinking ahead.

Seeking to solve the challenges mentioned above, providers and manufacturers together developed the USP (User Services Platform), defined by the Broadband Forum's TR-369 standard, which is the natural evolution of the TR-069. This new standard is designed to be **flexible, secure, scalable and standardized to meet the demands of a connected world today, and in the future.**


# Architecture

A brief explanation of the USP components and how it works.

## Controller

A USP controller manages a set of service elements represented in a USP agent’s data model. A USP controller database contains all agents within a deployment, capturing states and capabilities, and helps instantiate use case changes. The Controller is responsable to manage USP agents, interacting with the Agent Data Model executing CRUD operations and actions in the device.

## Agent

A USP Agent is responsible for exposing a set of "Service Elements" (essentially, a data model composed of objects and parameters that represent a specific set of functionality) for consumption by a Controller. While USP is capable of being used in many different environments, the home network is expected to be the most common environment, and in this environment a USP Agent would reside in a piece of Customer Premise Equipment (CPE), e.g. broadband home router, Wi-Fi access point, IoT gateway.

## Messages Encode

The messages exchanged betweeen a USP Agent and Controller might be encrypted with TLS. All the content is structured using [ProtoBuffers](https://protobuf.dev/), which is a spec from google that aims to encode messages with a small resource footprint usage along with other modern concepts, that helps to reduce the bandwidth consumed by the remote management protocol and have a faster parsing and decoding.

## Topology

<figure><img src="https://usp.technology/specification/architecture/usp_architecture.png" alt=""><figcaption><p>Figure 1 – USP Agent and Controller Architecture</p></figcaption></figure>

![USP training slides](https://github.com/leandrofars/oktopus/assets/83298718/b1d5a0c7-4567-464c-bc9b-1956ef5c5f3b)

![USP training slides](https://github.com/leandrofars/oktopus/assets/83298718/7b46dc1f-5eb2-4a1b-8e77-376b0836948a)

## Protocols

The [Message Transfer Protocol (MTP) ](/usp-tr-369/mtps)can be STOMP, Websockets or MQTTv5 and MQTTv3.1.1. The data is criptographed with TLS or DTLS and the messages are exchanged with [ProtoBuff](https://protobuf.dev/) encoding, having a USP Record that encapsulates a USP Message.

![image](https://github.com/leandrofars/oktopus/assets/83298718/9b789f0b-cb0c-4cec-8b8e-767ba21bffae)

## Notifications and Data Collection

You can create notifications that fire on a value change, object creation and removal, complete operation, or an event. It's also possible to create bulk data instances to send data periodically from a device to a USP Controller.

![USP training slides](https://github.com/leandrofars/oktopus/assets/83298718/184899a3-52e7-491a-8ee7-7b442fe50719)

## [OBUSPA](https://github.com/BroadbandForum/obuspa) **(Open Broadband User Services Platfrom Agent):**

Open Broadband-User Services Platform-Agent (OB-USP-Agent) is an open source project that is focused on creating a reference implementation of the User Services Platform (USP) [specification](https://usp.technology/) from an "Agent" perspective.

* Designed for embedded software (\~400kb on ARM)
* Encoded in C
* License [BSD 3-Clause](https://opensource.org/license/bsd-3-clause/)
* Made for Linux environments

## Data Analysis

The protocol has a mechanism called "Bulk Data", where it is possible to collect large volumes of data from the device, the data can be collected by HTTP, or another telemetry MTP defined in the TR standard, this data can be in JSON, CSV format or XML. This generates the opportunity to use AI on top of this data, obtaining relevant information that can be used for different purposes, from predicting events, KPIs, information for the commercial area, but also for the best configuration of a device.

## WiFi

It has over 130 Wi-Fi configuration and diagnostics metrics, many of these settings and parameters are a trade-off between signal coverage area, latency and throughput. When deploying Wi-Fi systems, there is a tendency to maintain the same configuration on all clients, causing the technology to perform below expectations. Machine Learning combined with the data analysis mentioned in the previous topic makes it possible to automate the management and optimization of Wireless networks, where a big data approach is able to find the ideal configuration for each device.

![image](https://github.com/leandrofars/oktopus/assets/83298718/3d6fe3e8-3ca2-460b-9583-da89b42753f8)

## Commands

It is possible to perform commands remotely on the product, such as: firmware update, reboot, reset, search for neighboring networks, backup, ping, network diagnostics and many others.

## IoT

<figure><img src="https://github.com/leandrofars/oktopus/assets/83298718/73e2e360-d53e-494e-9a50-60c83dae75df" alt=""><figcaption><p>Figure 34 – IoT proxied device model</p></figcaption></figure>

<figure><img src="https://github.com/leandrofars/oktopus/assets/83298718/91a87f43-3de7-42bd-a689-a4e14eecf5c0" alt=""><figcaption><p>Figure 33 – IoT individual device models</p></figcaption></figure>

<figure><img src="https://github.com/leandrofars/oktopus/assets/83298718/a2a12d9d-05a0-428b-ba3f-1ad83c876301" alt=""><figcaption><p>Figure 32 – IoT Data Model</p></figcaption></figure>

## Software Modules

Currently, telecommunications giants and startups, publishing new software daily, slow delivery cycles and manual and time-consuming quality assurance processes make it difficult for integrators and service providers to compete. USP "Software Module Management" allows a containerized approach to the development of software for embedded devices, making it possible to drastically reduce the chance of error in software updates, it also facilitates the integration of third parties in a device, still keeping the firmware part isolated from Vendor.<br>

<figure><img src="https://github.com/leandrofars/oktopus/assets/83298718/64664b0e-81cd-4a29-bbc5-b4186a04dfa2" alt=""><figcaption><p>Figure 28 – Possible Multi-Execution Environment Implementation</p></figcaption></figure>


# Introduction

Technical Report 069

## What it is?

CWMP – CPE WAN Management Protocol – is an application layer protocol. It was defined by [Broadband Forum](https://www.broadband-forum.org/) in [TR-069 Technical Report](https://www.broadband-forum.org/pdfs/tr-069-1-6-1.pdf), for remote management of customer-premises equipment (CPE) connected to an Internet Protocol (IP) network. TR-069 report describes the CPE WAN Management Protocol, intended for communication between a CPE and Auto-Configuration Server (ACS).

### Whats is it capable to manage?

The protocol addresses telecom devices, such as modems, routers, gateways, as well as end-user devices that connect to the Internet, such as set-top boxes, and VoIP-phones.

### What is the impact in the telco industry?

CWMP was created 20 years ago in 2004, since then it had a lot of improvements and ammendts added to It. Nowadays there are more than one billion devices being managed through CWMP around the globe.


# Architecture

The CPE communicates to a HTTP server, also called Auto Configuration Server (ACS), because instead of a normal client-server relationship between the device and the HTTP server, what happens is that the connection can be initiated from the server to the device.

<figure><img src="/files/WMPB1Pb1cqx29Ebntbpf" alt=""><figcaption><p>TR-069 Issue Amendment 6 Corrigendum 1 - Section 3.7.3 - FIgure 3</p></figcaption></figure>

As you can see above, the ACS and the CPE are at the same time a HTTP client and server, it's needed to exchange messages according to the CWMP protocol and because they're both a client and a server, must not exist [NAT](https://en.wikipedia.org/wiki/Network_address_translation) between the CPE and the ACS, they need to be directly accessible to each other.

Although there are some workarounds for the NAT problem as to use [XMPP](broken://spaces/ys3ycSw4qx1SjsJeAo2N) or [STUN](https://en.wikipedia.org/wiki/STUN), the best option is still to install the ACS with direct connection to the CPE, this simplifies your deployment and ensures a smooth solution.

## Messages

Here are the main messages you can send to a CPE and to interact with it. All of them are available in the [Oktopus REST API ](https://documenter.getpostman.com/view/18932104/2s93eR3vQY#96986790-3e2c-44fd-bc8c-9a6208f01516)> Device > CWMP Native Messages.

* GetParameterNames
* GetParameterValues
* GetParameterAttributes
* SetParameterValues
* AddObject
* DeleteObject

## Data Model

The first Data Model created to be used with CWMP was the [TR-098](https://cwmp-data-models.broadband-forum.org/tr-098-1-8-0.html), after that came that the [TR-181](https://usp-data-models.broadband-forum.org/tr-181-2-16-0-usp.html) which has more capabilities and possibilities than it's predecessor.

## Encoding

The messages between the CPE and the ACS are exchanged using [SOAP](https://www.geeksforgeeks.org/basics-of-soap-simple-object-access-protocol/), that transfer messages in the format of [XML](https://en.wikipedia.org/wiki/XML), and contains structured data with [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) information.


# Bibliography

Thanks to the people how helped with extremely usefull content and guidance. Here are some of the sources we used to build this documentation:

<https://friendly-tech.com/glossary/cwmp/>\
<https://usp.technology/>\
<https://www.broadband-forum.org/download/MU-461.pdf>\
<https://drive.google.com/drive/folders/1N7FqK0PkDhjCN5s3OhQ_wmz9UcTSwRCX>\
<https://usp.technology/specification/index.htm>\
<https://www.incognito.com/insights/usp-tr369>\
<https://usp-data-models.broadband-forum.org/tr-181-2-16-0-usp.html>\
<https://cwmp-data-models.broadband-forum.org/tr-098-1-8-0.html>\
<https://www.broadband-forum.org/pdfs/tr-069-1-6-1.pdf>


