Chapter 199: SNMP Agent Implementation

Chapter Objectives

Upon completing this chapter, you will be able to:

  • Understand the fundamentals of the Simple Network Management Protocol (SNMP).
  • Explain the roles of an SNMP Manager and an SNMP Agent.
  • Describe the structure and purpose of a Management Information Base (MIB) and Object Identifiers (OIDs).
  • Configure and enable the lwIP SNMP agent in an ESP-IDF project.
  • Implement custom MIB objects (scalars and potentially simple tables) on an ESP32.
  • Handle SNMP GET and SET requests from an SNMP Manager.
  • Send SNMP TRAP notifications from an ESP32.
  • Use common SNMP tools to interact with an ESP32 SNMP agent.
  • Recognize resource considerations for running an SNMP agent on various ESP32 variants.

Introduction

In the realm of network management, the Simple Network Management Protocol (SNMP) stands as a ubiquitous and time-tested standard. It provides a framework for monitoring and managing network devices, such as routers, switches, servers, printers, and, increasingly, embedded devices like the ESP32. By implementing an SNMP agent on an ESP32, you can expose its status, performance metrics, and configuration parameters to centralized Network Management Systems (NMS).

Imagine an ESP32 deployed in a remote location, perhaps as part of a sensor network or an industrial control system. An SNMP agent running on this ESP32 would allow network administrators to:

  • Query its operational status (e.g., uptime, network connectivity, free memory).
  • Read sensor values or application-specific data.
  • Configure certain parameters remotely (if SET operations are enabled).
  • Receive asynchronous notifications (TRAPs) about critical events (e.g., a sensor exceeding a threshold, a system error).

This chapter will guide you through implementing an SNMP agent on an ESP32 using the lwIP SNMP stack provided within the ESP-IDF v5.x framework. We will cover the theoretical concepts of SNMP and then proceed to practical examples, enabling your ESP32 to be monitored and managed using standard SNMP tools.

Theory

SNMP Fundamentals

SNMP is an application-layer protocol that facilitates the exchange of management information between network devices. It operates based on a few key components:

  1. SNMP Manager (or NMS – Network Management System):
    • A software application (often running on a dedicated server) that monitors and controls managed devices.
    • It sends requests to SNMP agents to query or modify data.
    • It receives asynchronous notifications (TRAPs or INFORMs) from agents.
    • Examples: Nagios, Zabbix, SolarWinds NPM, or simple command-line tools like snmpget, snmpset, snmpwalk.
  2. SNMP Agent:
    • A software module that runs on a managed device (e.g., our ESP32).
    • It maintains a database of managed objects (the MIB).
    • It responds to requests from SNMP managers (e.g., providing data values).
    • It can send TRAPs to managers to signal significant events.
  3. Managed Device:
    • A network node that contains an SNMP agent and resides on a managed network. ESP32 will be our managed device.
  4. Management Information Base (MIB):
    • A hierarchical collection of information about a managed device. Think of it as a database schema for the device’s manageable attributes.
    • Each piece of manageable information in the MIB is called a managed object (or MIB object, MIB variable).
    • MIBs define the properties of these managed objects, such as their data type, access level (read-only, read-write), and their unique identifier.
  5. Object Identifier (OID):
    • Each managed object in a MIB is uniquely identified by an Object Identifier (OID).
    • OIDs are structured hierarchically, forming a tree. They are represented as a sequence of integers separated by dots.
    • Example: .1.3.6.1.2.1.1.1.0 refers to sysDescr.0 (system description) from the standard MIB-II.
    • The OID tree has well-defined branches:
      • iso (1) -> org (3) -> dod (6) -> internet (1)
        • mgmt (2) -> mib-2 (1) (Standard MIB objects like system, interfaces, ip, etc.)
        • private (4) -> enterprise (1) (Vendor-specific MIBs. Each vendor gets a unique Enterprise ID).
graph TD
    subgraph "SNMP Manager (NMS)"
        direction LR
        M[<br><b>SNMP Manager</b><br>e.g., Zabbix, Nagios,<br>snmpget/snmpset CLI]
    end

    subgraph "Managed Device"
        direction TB
        subgraph "ESP32"
            A[<b>SNMP Agent</b><br>Runs on the device] --> D{{<br><b>MIB</b><br>Management<br>Information<br>Base}}
            D --> H((Hardware<br>Sensors, GPIOs))
            D --> S((Software<br>Uptime, Heap Size))
        end
    end

    M -- "<b>GET Request</b><br><i>(e.g., Get Uptime)</i>" --> A
    A -- "<b>SET Request</b><br><i>(e.g., Set LED State)</i>" --- M
    A -- "<b>RESPONSE</b><br><i>(e.g., Uptime is 12345)</i>" --> M
    A -.-> M;
    subgraph Traps
    T("<b>TRAP / INFORM</b><br><i>(e.g., Sensor Threshold Exceeded!)</i>")
    end
    A -- "Asynchronous Event" --> T

    classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px;
    classDef manager-group fill:transparent,stroke:none;

    class M fill:#DBEAFE,stroke:#2563EB,stroke-width:1px;
    class A fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px;
    class D fill:#FEF3C7,stroke:#D97706,stroke-width:1px;
    class H,S fill:#E0E7FF,stroke:#4338CA,stroke-width:1px;
    class T fill:#FEE2E2,stroke:#DC2626,stroke-width:1px;

graph TD
    subgraph OID Tree Structure
        direction TB
        R(("<br><b>Root</b><br><i>(unnamed)</i>"))
        R --> ISO(<b>iso</b><br><i>.1</i>)
        ISO --> ORG(<b>org</b><br><i>.1.3</i>)
        ORG --> DOD(<b>dod</b><br><i>.1.3.6</i>)
        DOD --> Internet(<b>internet</b><br><i>.1.3.6.1</i>)

        Internet --> MGMT(<b>mgmt</b><br><i>.1.3.6.1.2</i>)
        Internet --> PRIVATE(<b>private</b><br><i>.1.3.6.1.4</i>)

        MGMT --> MIB2(<b>mib-2</b><br><i>.1.3.6.1.2.1</i>)
        MIB2 --> SYS(<b>system</b><br><i>.1.3.6.1.2.1.1</i>)
        MIB2 --> IF(<b>interfaces</b><br><i>.1.3.6.1.2.1.2</i>)
        SYS --> descr("<b>sysDescr</b><br><i>(...1.1)</i>")
        SYS --> uptime("<b>sysUpTime</b><br><i>(...1.3)</i>")

        PRIVATE --> ENT(<b>enterprise</b><br><i>.1.3.6.1.4.1</i>)
        ENT --> espressif("<b>Espressif (27014)</b><br><i>...1.4.1.27014</i>")
        ENT --> custom("<b>Your Custom PEN (e.g., 99999)</b><br><i>...1.4.1.99999</i>")
        custom --> prod("<b>yourProduct (1)</b><br><i>...99999.1</i>")
        prod --> mod("<b>yourModule (1)</b><br><i>...99999.1.1</i>")
        mod --> temp("<b>esp32Temperature (1.0)</b><br><i>...1.1.1.0</i>")
        mod --> led("<b>esp32LedState (2.0)</b><br><i>...1.1.2.0</i>")

    end

    classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px
    classDef rootNodes fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef mgmtNodes fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef sysNodes fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3
    classDef privateNodes fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    classDef customNodes fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E

    class R,ISO,ORG,DOD,Internet rootNodes
    class MGMT,MIB2,SYS,IF mgmtNodes
    class descr,uptime sysNodes
    class PRIVATE,ENT privateNodes
    class espressif,custom,prod,mod,temp,led customNodes

SNMP Versions

  • SNMPv1: The original version. Simple but lacks robust security (uses plain-text “community strings” for authentication).
  • SNMPv2c: (c for “community-based”) Extends SNMPv1 with improved performance (e.g., GETBULK operation) and more detailed error reporting, but still uses community strings for security. This is commonly implemented in embedded systems due to its relative simplicity.
  • SNMPv3: The most secure version. Provides strong authentication (e.g., MD5, SHA) and encryption (e.g., DES, AES) for messages. It introduces a User-based Security Model (USM) and View-based Access Control Model (VACM). Implementing SNMPv3 is significantly more complex and resource-intensive, especially for constrained devices.
Feature SNMPv1 SNMPv2c SNMPv3
Security Model Community Strings (Plain Text) Community Strings (Plain Text) User-based Security Model (USM)
Authentication None (Community string as a weak password) None (Community string as a weak password) Strong (MD5, SHA)
Encryption None None Strong (DES, AES)
Key Operations GET, SET, GETNEXT, TRAP GET, SET, GETNEXT, GETBULK, TRAP, INFORM GET, SET, GETNEXT, GETBULK, TRAP, INFORM
ESP32 Suitability Supported by lwIP, but v2c is preferred. Well-suited. Good balance of features and resource usage for ESP32. Most common choice. Complex and resource-intensive. Not natively supported by the default lwIP stack. Requires extra libraries and significant resources.
Primary Use Case Legacy systems. Most common for device monitoring in secure internal networks where simplicity is valued. Required for high-security environments and managing devices over public networks.

For ESP32 applications where simplicity and resource constraints are factors, SNMPv2c is often the starting point. The lwIP stack included with ESP-IDF primarily supports SNMPv1 and SNMPv2c.

SNMP Operations (Protocol Data Units – PDUs)

SNMP communication involves different types of messages (PDUs):

Operation (PDU) Direction Purpose Example
GET Manager → Agent Retrieve the value of one or more specific OIDs. “What is the current value of sysDescr.0?”
SET Manager → Agent Modify the value of a writable OID. “Set the value of esp32LedState.0 to 1.”
GETNEXT Manager → Agent Discover the MIB by retrieving the value of the very next OID in the tree. “After sysDescr.0, what’s the next available OID?”
GETBULK (v2c/v3) Manager → Agent Efficiently retrieve a large block of data with a single request. An optimized GETNEXT. “Give me the next 10 OIDs and their values starting from here.”
RESPONSE Agent → Manager The agent’s reply to a GET, SET, GETNEXT, or GETBULK request. sysDescr.0 is ‘ESP32 IoT Device’.”
TRAP Agent → Manager Asynchronously report a significant event. Unconfirmed delivery. “Alert! A device restart has occurred!”
INFORM (v2c/v3) Agent → Manager A confirmed version of a TRAP. The manager must send an acknowledgment. “Alert! Link down on interface 2. Please confirm you received this.”

Community Strings

In SNMPv1 and SNMPv2c, community strings are used as a simple form of authentication. They are like passwords shared between the manager and the agent.

  • Read-Only Community String: Allows managers to read MIB objects (e.g., “public” by convention).
  • Read-Write Community String: Allows managers to read and write MIB objects (e.g., “private” by convention, but should always be changed for security).

Warning: Community strings are transmitted in plain text and offer weak security. They should be changed from defaults and protected by network access controls if possible. For secure environments, SNMPv3 is strongly recommended, though it’s more complex for ESP32.

Defining a Private MIB for ESP32

While standard MIBs (like MIB-II under .1.3.6.1.2.1) provide common system information, you’ll often need to define custom MIB objects specific to your ESP32 application. These are placed under the private.enterprise branch of the OID tree.

  1. Obtain an Enterprise ID (Optional for Testing): Officially, you would get a unique Private Enterprise Number (PEN) from IANA. For testing and internal use, you can use an OID from experimental branches or a non-official PEN. Espressif Systems has the PEN 27014. So, a custom MIB for an Espressif device could start with .1.3.6.1.4.1.27014.... For simplicity in examples, we might use a more generic private OID.
  2. Structure Your MIB: Define a hierarchy for your custom objects. For example:.iso.org.dod.internet.private.enterprise.yourEnterpriseID.yourProduct.yourModule.yourObject.1.3.6.1.4.1.yourEnterpriseID.1.1.0 (e.g., temperature sensor value).1.3.6.1.4.1.yourEnterpriseID.1.2.0 (e.g., LED status)
  3. Define MIB Objects: For each object:
    • Name: A human-readable name (e.g., esp32Temperature).
    • OID: Its unique Object Identifier.
    • Syntax/Data Type: (e.g., INTEGER, OCTET STRING, Counter32, Gauge32).
    • Access: (read-only, read-write, not-accessible).
    • Description: A textual description.

These definitions are formally written in a MIB file (using SMIv1 or SMIv2 syntax), which can be loaded by SNMP managers. However, when implementing the agent, you define these objects and their behavior in C code.

lwIP SNMP Agent

The ESP-IDF uses the lwIP TCP/IP stack, which includes an optional SNMP agent module.

  • Features: Supports SNMPv1 and SNMPv2c, GET, GETNEXT, SET operations, and TRAPs.
  • MIB Implementation: Provides mechanisms to define scalar MIB objects and simple tables. Standard MIB-II objects (system group, interfaces group, etc.) are often partially pre-implemented.
  • Customization: You link C functions (callbacks) to your custom OIDs. These functions are executed when a manager accesses those OIDs.
    • get_value: A function that returns the current value of the MIB object.
    • set_test and set_value: Functions to validate and apply a new value for read-write objects.

Practical Examples

This section will guide you through enabling and configuring the lwIP SNMP agent on an ESP32, defining custom MIB objects, and interacting with them using SNMP tools.

Prerequisites:

  1. ESP-IDF v5.x toolchain with VS Code.
  2. An ESP32 development board.
  3. A Wi-Fi network.
  4. SNMP manager tools installed on your computer.
    • Linux/macOS: The net-snmp package provides snmpget, snmpset, snmpwalk, snmptrapd. (e.g., sudo apt install snmp or brew install net-snmp).
    • Windows: You can install Net-SNMP for Windows, or use GUI tools like Paessler SNMP Tester, iReasoning MIB Browser, or ManageEngine MibBrowser Free Tool.

Step 1: Create and Configure Project

  1. Create a new ESP-IDF project (e.g., esp32_snmp_agent) based on the hello_world example.
  2. Configure the project for Wi-Fi and SNMP:Run idf.py menuconfig.
    • Wi-Fi Configuration:Navigate to Example Connection Configuration —> and set your WiFi SSID and WiFi Password.
    • Enable lwIP SNMP Agent:Navigate to Component config —> LWIP —> SNMP —>.
      • Check [*] Enable SNMP agent.
      • SNMP server port: Default is 161.
      • SNMP community: Set the read-only community string (e.g., public).
      • SNMP write community: Set the read-write community string (e.g., private). Change these defaults for any real deployment!
      • SNMP trap destination IP address: Enter the IP address of the machine where your SNMP manager/trap receiver will run.
      • SNMP trap destination port: Default is 162.
      • You can enable MIB-II modules if needed (e.g., [*] Enable SNMP MIB2 system group).
    • Increase Main Task Stack Size:Navigate to Component config —> ESP System Settings —> Main task stack size.Increase it to at least 4096 or 6144 bytes. The SNMP agent itself doesn’t require a huge stack if callbacks are efficient, but it’s good practice.
    Save the configuration and exit menuconfig.

Step 2: Define Custom MIB and Implement Callbacks

We will define a few custom MIB objects under a private enterprise OID. Let’s use a placeholder enterprise OID: .1.3.6.1.4.1.99999 (replace 99999 with your actual PEN if you have one, or use this for testing).

  • .1.3.6.1.4.1.99999.1.1.0: esp32DeviceStatus (OCTET STRING, read-only) – e.g., “Running”
  • .1.3.6.1.4.1.99999.1.2.0: esp32Uptime (TimeTicks, read-only) – uptime in centiseconds
  • .1.3.6.1.4.1.99999.1.3.0: esp32LedState (INTEGER, read-write) – 0 for OFF, 1 for ON (controls a hypothetical LED)

Create a new header file main/custom_mib.h:

C
// main/custom_mib.h
#ifndef CUSTOM_MIB_H
#define CUSTOM_MIB_H

#include "lwip/apps/snmp_opts.h"
#include "lwip/apps/snmp_core.h"

// Define our private enterprise OID base
// .iso(1).org(3).dod(6).internet(1).private(4).enterprise(1).yourEnterpriseID(99999)
#define CUSTOM_ENTERPRISE_OID_BASE {1, 3, 6, 1, 4, 1, 99999}
#define CUSTOM_ENTERPRISE_OID_LEN 7 // Length of the base OID

// Our custom MIB objects under this base
// .1.3.6.1.4.1.99999.1.1.0 : esp32DeviceStatus (OCTET_STRING, RO)
// .1.3.6.1.4.1.99999.1.2.0 : esp32Uptime (TIMETICKS, RO)
// .1.3.6.1.4.1.99999.1.3.0 : esp32LedState (INTEGER, RW)

// These are leaf identifiers relative to CUSTOM_ENTERPRISE_OID_BASE.productGroup.moduleGroup
// For simplicity, let's assume productGroup = 1, moduleGroup = 1
// So, esp32DeviceStatus will be CUSTOM_ENTERPRISE_OID_BASE + .1.1.0
// esp32Uptime will be CUSTOM_ENTERPRISE_OID_BASE + .1.2.0
// esp32LedState will be CUSTOM_ENTERPRISE_OID_BASE + .1.3.0


// Structure to hold the state for our custom MIB objects (e.g., LED state)
typedef struct {
    int32_t led_state; // 0 for OFF, 1 for ON
    // Add other states here if needed
} custom_mib_states_t;

extern custom_mib_states_t g_custom_mib_states;

void custom_mib_init(void);

// Optional: function to simulate an event and send a trap
void send_custom_trap_example(void);


#endif // CUSTOM_MIB_H

Create a new C file main/custom_mib.c:

C
// main/custom_mib.c
#include "custom_mib.h"
#include "esp_system.h" // For esp_log.h, esp_timer_get_time
#include "esp_log.h"
#include "lwip/apps/snmp_scalar.h"
#include "lwip/apps/snmp_agent.h" // For snmp_send_trap_specific etc.
#include "lwip/apps/snmp_msg.h"   // For snmp_varbind_alloc etc.

static const char *TAG_MIB = "CUSTOM_MIB";

custom_mib_states_t g_custom_mib_states = {
    .led_state = 0 // Initial LED state is OFF
};

// --- esp32DeviceStatus Callbacks (.1.3.6.1.4.1.99999.1.1.0) ---
static s16_t get_device_status(struct snmp_node_instance* instance, void* value) {
    const char *status = "Running";
    // Ensure value buffer is large enough. SNMP_MAX_VALUE_SIZE defined in snmp_opts.h or lwipopts.h
    strncpy((char*)value, status, SNMP_MAX_VALUE_SIZE); 
    ((char*)value)[SNMP_MAX_VALUE_SIZE -1] = '\0'; // Ensure null termination
    return strlen(status);
}

// --- esp32Uptime Callbacks (.1.3.6.1.4.1.99999.1.2.0) ---
static s16_t get_uptime(struct snmp_node_instance* instance, void* value) {
    uint32_t uptime_ticks = (uint32_t)(esp_timer_get_time() / 10000); // esp_timer_get_time is in microseconds, timeticks are 1/100 sec
    *(uint32_t*)value = uptime_ticks;
    return sizeof(uint32_t);
}

// --- esp32LedState Callbacks (.1.3.6.1.4.1.99999.1.3.0) ---
static s16_t get_led_state(struct snmp_node_instance* instance, void* value) {
    *(s32_t*)value = g_custom_mib_states.led_state;
    return sizeof(s32_t);
}

static snmp_err_t set_led_state_test(struct snmp_node_instance* instance, u16_t len, void* value) {
    s32_t new_state = *(s32_t*)value;
    if (new_state == 0 || new_state == 1) {
        return SNMP_ERR_NOERROR;
    }
    return SNMP_ERR_WRONGVALUE;
}

static snmp_err_t set_led_state_commit(struct snmp_node_instance* instance, u16_t len, void* value) {
    s32_t new_state = *(s32_t*)value;
    g_custom_mib_states.led_state = new_state;
    ESP_LOGI(TAG_MIB, "LED state set to: %s", new_state == 1 ? "ON" : "OFF");
    // Here you would actually control an LED GPIO
    // gpio_set_level(LED_GPIO, new_state);
    return SNMP_ERR_NOERROR;
}

// Define the scalar MIB nodes
// Node OIDs: Base + ProductGroup + ModuleGroup + Leaf
// Example: .1.3.6.1.4.1.99999.1.1.1.0 (DeviceStatus) - adding .0 for scalar instance
// Note: lwIP SNMP scalar nodes automatically append '.0' to the OID you define.
// So, if you define OID {CUSTOM_ENTERPRISE_OID_BASE, 1, 1, 1}, it becomes ...99999.1.1.1.0 when queried.

// Product Group OID part (e.g., 1)
#define PRODUCT_GROUP_OID 1
// Module Group OID part (e.g., 1 for generic device info, 2 for sensor module etc.)
#define MODULE_GROUP_OID 1

// Leaf OIDs
#define DEVICE_STATUS_OID_LEAF  1
#define UPTIME_OID_LEAF         2
#define LED_STATE_OID_LEAF      3

// OID for esp32DeviceStatus: CUSTOM_ENTERPRISE_OID_BASE + .PRODUCT_GROUP_OID.MODULE_GROUP_OID.DEVICE_STATUS_OID_LEAF
static const struct snmp_scalar_array_node_def esp32_device_status_node =
    SNMP_SCALAR_CREATE_NODE_READONLY(DEVICE_STATUS_OID_LEAF, SNMP_ASN1_TYPE_OCTET_STRING, get_device_status);

// OID for esp32Uptime: CUSTOM_ENTERPRISE_OID_BASE + .PRODUCT_GROUP_OID.MODULE_GROUP_OID.UPTIME_OID_LEAF
static const struct snmp_scalar_array_node_def esp32_uptime_node =
    SNMP_SCALAR_CREATE_NODE_READONLY(UPTIME_OID_LEAF, SNMP_ASN1_TYPE_TIMETICKS, get_uptime);

// OID for esp32LedState: CUSTOM_ENTERPRISE_OID_BASE + .PRODUCT_GROUP_OID.MODULE_GROUP_OID.LED_STATE_OID_LEAF
static const struct snmp_scalar_array_node_def esp32_led_state_node =
    SNMP_SCALAR_CREATE_NODE(LED_STATE_OID_LEAF, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_ACCESS_READ_WRITE, get_led_state, set_led_state_test, set_led_state_commit);


// Array of pointers to our scalar MIB nodes
static const struct snmp_scalar_array_node* const custom_mib_nodes[] = {
    &esp32_device_status_node.node.node, // Need to get the inner snmp_scalar_node
    &esp32_uptime_node.node.node,
    &esp32_led_state_node.node.node
};

// Create the MIB tree structure
// This defines the ".1.3.6.1.4.1.99999.1.1" part of the OID tree
// where our scalar nodes will reside.
static const u32_t enterprise_product_module_oid_base[] = {CUSTOM_ENTERPRISE_OID_BASE, PRODUCT_GROUP_OID, MODULE_GROUP_OID};
static const struct snmp_tree_node custom_mib_product_module_branch =
    SNMP_CREATE_TREE_NODE(MODULE_GROUP_OID, custom_mib_nodes); // Module group under product group

static const struct snmp_tree_node* const product_group_nodes[] = {
    &custom_mib_product_module_branch.node // custom_mib_product_module_branch is already a snmp_node
};
static const struct snmp_tree_node custom_mib_product_branch = 
    SNMP_CREATE_TREE_NODE(PRODUCT_GROUP_OID, product_group_nodes); // Product group under enterprise

// Root of our custom MIB structure under .1.3.6.1.4.1.99999 (CUSTOM_ENTERPRISE_OID_BASE)
static const u32_t private_enterprise_oid[] = {CUSTOM_ENTERPRISE_OID_BASE}; // This is just the enterprise ID itself
struct snmp_node_instance custom_mib_enterprise_root_instance; // Instance for the root

void custom_mib_init(void) {
    ESP_LOGI(TAG_MIB, "Initializing Custom MIB");

    // Register the root of our custom MIB structure.
    // This makes .1.3.6.1.4.1.99999 accessible.
    // Then, we add our 'product_branch' under this.
    // The '99999' is the last element of private_enterprise_oid.
    // The parent OID for snmp_register_tree_node_instance should be .1.3.6.1.4.1
    // And the node OID itself will be 99999.

    static const struct snmp_tree_node* const enterprise_nodes[] = {&custom_mib_product_branch.node};
    const struct snmp_tree_node enterprise_root_node = SNMP_CREATE_TREE_NODE(private_enterprise_oid[CUSTOM_ENTERPRISE_OID_LEN-1], enterprise_nodes);
    
    // snmp_mib2_ दूसरा arg parent OID लेता है, इसलिए .1.3.6.1.4.1 को hardcode करने के बजाय,
    // हम SNMP_NODE_OID_PRIVATE_ENTERPRISE का उपयोग करते हैं, जो SNMP मानक OID ट्री में सही स्थान है
    // निजी उद्यम नोड्स के लिए।
    // The OID for snmp_register_tree_node_instance is the OID of the node itself,
    // not its parent. The parent is implicitly .1.3.6.1.4.1 (SNMP_NODE_OID_ENTERPRISE)
    snmp_register_tree_node(SNMP_NODE_OID_ENTERPRISE, &enterprise_root_node.node);

    ESP_LOGI(TAG_MIB, "Custom MIB initialized.");
    ESP_LOGI(TAG_MIB, "DeviceStatus OID: .1.3.6.1.4.1.99999.%d.%d.%d.0", PRODUCT_GROUP_OID, MODULE_GROUP_OID, DEVICE_STATUS_OID_LEAF);
    ESP_LOGI(TAG_MIB, "Uptime OID:       .1.3.6.1.4.1.99999.%d.%d.%d.0", PRODUCT_GROUP_OID, MODULE_GROUP_OID, UPTIME_OID_LEAF);
    ESP_LOGI(TAG_MIB, "LedState OID:     .1.3.6.1.4.1.99999.%d.%d.%d.0", PRODUCT_GROUP_OID, MODULE_GROUP_OID, LED_STATE_OID_LEAF);
}


void send_custom_trap_example(void) {
    // Define a specific trap OID under our enterprise: .1.3.6.1.4.1.99999.2.1
    // (.1.3.6.1.4.1.yourEnterpriseID.yourTrapsGroup.yourSpecificTrapID)
    // For traps, the last sub-identifier is often 0.
    // The second to last is the specific trap number.
    // The enterprise OID itself is usually passed as part of the trap PDU.
    
    // Generic trap type for enterprise specific traps
    #define SNMP_TRAP_ENTERPRISE_SPECIFIC 6 

    // Our specific trap ID (e.g., 1 for "device restart", 2 for "sensor alert")
    u32_t specific_trap_id = 1; // Example: Sensor Alert Trap

    // The OID that defines this trap. This is usually your enterprise OID + a traps sub-identifier + specific trap id.
    // For example: .1.3.6.1.4.1.99999 (enterprise) .2 (traps group) .1 (specific trap)
    // This full OID is sent as the "snmpTrapOID.0" varbind in an SNMPv2c trap.
    oid_t trap_oid_val[] = {CUSTOM_ENTERPRISE_OID_BASE, 2, specific_trap_id}; // .1.3.6.1.4.1.99999.2.1
    struct snmp_obj_id trap_oid;
    SNMP_OBJ_ID_SET(&trap_oid, trap_oid_val, LWIP_ARRAYSIZE(trap_oid_val));


    // Optional: Add variable bindings (varbinds) to the trap PDU to provide more context
    struct snmp_varbind *vb = snmp_varbind_alloc(&trap_oid, SNMP_ASN1_TYPE_OCTET_STRING, strlen("Sensor threshold exceeded"));
    if (vb == NULL) {
        ESP_LOGE(TAG_MIB, "Failed to allocate varbind for trap");
        return;
    }
    memcpy(vb->value, "Sensor threshold exceeded", strlen("Sensor threshold exceeded"));
    
    // Add another varbind, e.g., current temperature that caused the alert
    // For this, we'd need the OID of our temperature sensor
    oid_t temp_oid_val[] = {CUSTOM_ENTERPRISE_OID_BASE, PRODUCT_GROUP_OID, MODULE_GROUP_OID, DEVICE_STATUS_OID_LEAF, 0}; // Using device status OID for example
    struct snmp_obj_id temp_obj_oid;
    SNMP_OBJ_ID_SET(&temp_obj_oid, temp_oid_val, LWIP_ARRAYSIZE(temp_oid_val));
    
    s32_t current_temp_val = 35; // Example value
    struct snmp_varbind *vb_temp = snmp_varbind_alloc(&temp_obj_oid, SNMP_ASN1_TYPE_INTEGER, sizeof(s32_t));
    if(vb_temp == NULL) {
        ESP_LOGE(TAG_MIB, "Failed to allocate temp varbind for trap");
        snmp_varbind_free(vb); // Free previously allocated varbind
        return;
    }
    *(s32_t*)vb_temp->value = current_temp_val;
    vb->next = vb_temp; // Link varbinds

    ESP_LOGI(TAG_MIB, "Sending SNMP Trap for specific ID %lu", specific_trap_id);
    // For SNMPv2c/v3 traps, snmpTrapOID.0 is the first varbind.
    // The uptime is also automatically included as sysUpTimeInstance.0.
    // The enterprise OID used in snmp_send_trap_specific is often the agent's enterprise root.
    oid_t enterprise_oid_val[] = {CUSTOM_ENTERPRISE_OID_BASE};
    struct snmp_obj_id enterprise_obj_oid;
    SNMP_OBJ_ID_SET(&enterprise_obj_oid, enterprise_oid_val, LWIP_ARRAYSIZE(enterprise_oid_val));

    // For SNMPv2c, the trap OID itself is sent as the first varbind.
    // The `snmp_send_trap_specific` is more for SNMPv1 style traps if not careful.
    // For v2c, we should use `snmp_send_trap_ext` or build PDU manually.
    // lwIP's snmp_trap() or snmp_send_trap_specific() might be v1 oriented.
    // Let's try a more direct approach for SNMPv2 TRAP if available or a standard v1 trap.
    // The lwIP API `snmp_send_trap_specific` is for SNMPv1.
    // To send SNMPv2c trap, you usually set `snmp_trap_varbind_enterprise_oid = 0`
    // and provide the trap OID as the first varbind.
    
    // For simplicity, we'll use the generic `snmp_trap` which should adapt based on context or default to v1/v2c basics.
    // The modern way is `snmp_send_trap_ext(SNMP_TRAP_ENTERPRISE_SPECIFIC, specific_trap_id, &enterprise_obj_oid, vb);`
    // However, snmp_send_trap_ext might not be available or easy to use in all lwIP versions/configs directly.
    // The `snmp_send_trap_specific` is more for SNMPv1.
    // The `snmp_send_inform` is for INFORMs.
    // `snmp_v2_trap_ex` seems to be the internal function.
    //
    // The easiest way to send an SNMPv2c TRAP with specific OID and varbinds with lwIP's default agent:
    // 1. The first varbind MUST be sysUpTime.0 (SNMP_MSG_OID_SYSUPTIME, 0) - usually added automatically by snmp_send_trap()
    // 2. The second varbind MUST be snmpTrapOID.0 (SNMP_MSG_OID_SNMPTRAPOID, 0) with the value of your trap OID.
    // 3. Subsequent varbinds are your custom data.

    // Let's construct the varbind list for an SNMPv2c TRAP.
    // snmp_varbind_list_free(&vb_list_head); // Clear any previous
    // struct snmp_varbind *vb_list_head = NULL;
    // struct snmp_varbind *vb_list_tail = NULL;

    // // Varbind 1: snmpTrapOID.0 (Value is our trap OID: .1.3.6.1.4.1.99999.2.1)
    // oid_t snmptrapoid_oid_val[] = {SNMP_MSG_OID_SNMPTRAPOID, 0};
    // struct snmp_obj_id snmptrapoid_objid;
    // SNMP_OBJ_ID_SET(&snmptrapoid_objid, snmptrapoid_oid_val, LWIP_ARRAYSIZE(snmptrapoid_oid_val));
    // struct snmp_varbind *vbs_trapoid = snmp_varbind_alloc(&snmptrapoid_objid, SNMP_ASN1_TYPE_OBJECT_ID, trap_oid.len * sizeof(oid_t));
    // if(!vbs_trapoid) { ESP_LOGE(TAG_MIB, "Failed to alloc snmpTrapOID.0 varbind"); return; }
    // SNMP_OBJ_ID_COPY((struct snmp_obj_id *)vbs_trapoid->value, &trap_oid); // Value is the trap OID itself
    // snmp_varbind_tail_add(&vb_list_head, &vb_list_tail, vbs_trapoid);
    
    // // Varbind 2: Custom varbind (e.g., alert message)
    // // OID: .1.3.6.1.4.1.99999.1.1.0 (esp32DeviceStatus, using it as an example payload OID)
    // oid_t alert_msg_oid_val[] = {CUSTOM_ENTERPRISE_OID_BASE, PRODUCT_GROUP_OID, MODULE_GROUP_OID, DEVICE_STATUS_OID_LEAF, 0};
    // struct snmp_obj_id alert_msg_objid;
    // SNMP_OBJ_ID_SET(&alert_msg_objid, alert_msg_oid_val, LWIP_ARRAYSIZE(alert_msg_oid_val));
    // const char *alert_str = "High Temperature Alert!";
    // struct snmp_varbind *vbs_alert_msg = snmp_varbind_alloc(&alert_msg_objid, SNMP_ASN1_TYPE_OCTET_STRING, strlen(alert_str));
    // if(!vbs_alert_msg) { ESP_LOGE(TAG_MIB, "Failed to alloc alert_msg varbind"); snmp_varbind_list_free(&vb_list_head); return; }
    // MEMCPY(vbs_alert_msg->value, alert_str, strlen(alert_str));
    // snmp_varbind_tail_add(&vb_list_head, &vb_list_tail, vbs_alert_msg);

    // ESP_LOGI(TAG_MIB, "Sending SNMPv2c Trap with specific OID and varbinds");
    // snmp_send_trap(SNMP_GENTRAP_ENTERPRISE_SPECIFIC, &enterprise_obj_oid, specific_trap_id, vb_list_head);
    // snmp_varbind_list_free(&vb_list_head);
    
    // The above is complex. For a simpler trap using SNMPv1 style (often what basic lwIP `snmp_send_trap_specific` does):
    ESP_LOGI(TAG_MIB, "Sending SNMPv1-style Trap (specific %lu)", specific_trap_id);
    snmp_send_trap_specific(
        SNMP_TRAP_ENTERPRISE_SPECIFIC, // generic trap type = 6 for enterpriseSpecific
        specific_trap_id,              // specific trap code
        &enterprise_obj_oid,           // enterprise OID (.1.3.6.1.4.1.99999)
        vb                             // list of varbinds (can be NULL)
    );

    // Free the varbind list if it was allocated (vb points to the head)
    if (vb != NULL) {
        snmp_varbind_list_free(&vb);
    }
}

Step 3: Update Main Application Logic

Modify main/your_main_file.c:

C
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/sockets.h" // For getting IP address

// SNMP includes
#include "lwip/apps/snmp_opts.h" // For LWIP_SNMP
#if LWIP_SNMP
#include "lwip/apps/snmp.h"
#include "lwip/apps/snmp_core.h"
#include "lwip/apps/snmp_mib2.h"
#include "lwip/apps/snmp_agent.h" // For snmp_set_community_write etc.
#endif // LWIP_SNMP

#include "custom_mib.h" // Our custom MIB definitions

// Wi-Fi Configuration
#define EXAMPLE_ESP_WIFI_SSID      CONFIG_EXAMPLE_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_EXAMPLE_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY  CONFIG_EXAMPLE_MAXIMUM_RETRY

static const char *TAG_APP = "SNMP_APP";

/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1
static int s_retry_num = 0;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data) {
    // (Identical to event_handler from Chapter 197/198)
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG_APP, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG_APP,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG_APP, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void) {
    // (Identical to wifi_init_sta from Chapter 197/198)
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t* sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG_APP, "wifi_init_sta finished.");

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG_APP, "connected to ap SSID:%s", EXAMPLE_ESP_WIFI_SSID);
        esp_netif_ip_info_t ip_info;
        esp_netif_get_ip_info(sta_netif, &ip_info);
        ESP_LOGI(TAG_APP, "SNMP Agent IP address: " IPSTR, IP2STR(&ip_info.ip));

        // Configure SNMP Trap destination if not already set by menuconfig constants
        #if LWIP_SNMP
        // Note: CONFIG_LWIP_SNMP_TRAP_DESTINATION_IP is set via menuconfig
        // If you need to set it dynamically:
        // ip_addr_t trap_target_ip;
        // ipaddr_aton("192.168.1.100", &trap_target_ip); // Replace with your NMS IP
        // snmp_trap_dst_ip_set(0, &trap_target_ip);
        // snmp_trap_dst_enable(0, 1);
        ESP_LOGI(TAG_APP, "SNMP Trap Destination IP (from menuconfig): %s", CONFIG_LWIP_SNMP_TRAP_DESTINATION_IP);
        #endif

    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG_APP, "Failed to connect to SSID:%s", EXAMPLE_ESP_WIFI_SSID);
    } else {
        ESP_LOGE(TAG_APP, "UNEXPECTED WIFI EVENT");
    }
}

static void snmp_agent_task(void *pvParameters) {
#if LWIP_SNMP
    ESP_LOGI(TAG_APP, "Starting SNMP agent task");
    // Initialize SNMP agent
    snmp_init(); // This initializes MIB2 stuff as well if enabled

    // Set community strings (these might also be set via menuconfig)
    // If set via menuconfig (CONFIG_LWIP_SNMP_COMMUNITY, CONFIG_LWIP_SNMP_COMMUNITY_WRITE),
    // they are typically compiled in. Calling these functions overrides them.
    snmp_set_community((const char*)CONFIG_LWIP_SNMP_COMMUNITY);
    snmp_set_community_write((const char*)CONFIG_LWIP_SNMP_COMMUNITY_WRITE);
    
    // Enable authentication for SET requests using the write community
    snmp_set_write_access(SNMP_VERSION_2c); // Or SNMP_VERSION_1, ensure this matches your manager
                                           // For SNMPv2c, the set_community_write is used.
                                           // For SNMPv1, it's also set_community_write.
                                           // It implies that if a packet comes with the write community, it's allowed.

    // Initialize our custom MIB
    custom_mib_init();

    ESP_LOGI(TAG_APP, "SNMP Agent initialized. Ready to receive requests.");
    ESP_LOGI(TAG_APP, "Read Community: %s", CONFIG_LWIP_SNMP_COMMUNITY);
    ESP_LOGI(TAG_APP, "Write Community: %s", CONFIG_LWIP_SNMP_COMMUNITY_WRITE);


    // Periodically send a trap as an example
    uint32_t trap_counter = 0;
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(30000)); // Every 30 seconds
        ESP_LOGI(TAG_APP, "Sending example trap (%lu)...", trap_counter++);
        send_custom_trap_example();
    }
#else
    ESP_LOGE(TAG_APP, "SNMP is not enabled in lwIP. Please enable it in menuconfig.");
#endif //LWIP_SNMP
    vTaskDelete(NULL);
}

void app_main(void) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG_APP, "ESP_WIFI_MODE_STA");
    wifi_init_sta();

    EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG_APP, "Wi-Fi Connected. Starting SNMP agent task if enabled.");
        #if LWIP_SNMP
        // The SNMP agent runs within the lwIP TCP/IP task context after snmp_init().
        // You don't typically need a separate high-priority task just for SNMP polling,
        // as requests are handled by lwIP. This task is for initialization and perhaps periodic actions like sending traps.
        xTaskCreate(&snmp_agent_task, "snmp_agent_task", 4096, NULL, 5, NULL);
        #else
        ESP_LOGW(TAG_APP, "SNMP agent not started as LWIP_SNMP is disabled.");
        #endif
    } else {
        ESP_LOGE(TAG_APP, "Wi-Fi not connected. SNMP agent task will not start.");
    }
}

Step 4: Modify CMakeLists.txt

Ensure your main/CMakeLists.txt includes necessary source files and components:

Plaintext
# main/CMakeLists.txt
idf_component_register(SRCS "your_main_file.c" "custom_mib.c"
                    INCLUDE_DIRS "."
                    REQUIRES lwip esp_wifi nvs_flash)
# lwip component will be automatically linked if CONFIG_LWIP_SNMP_AGENT is set.

Step 5: Build, Flash, and Test

  1. Build, Flash, and Monitor your ESP32.Note the IP address of your ESP32 from the logs.
  2. Set up Trap Receiver (Optional but Recommended for Trap Testing):On your NMS machine (where you entered the IP in menuconfig), start a trap daemon.For net-snmp on Linux/macOS:sudo snmptrapd -f -Lo -c /etc/snmp/snmptrapd.conf # -f: run in foreground, -Lo: log to stdout # You might need a simple snmptrapd.conf, e.g., containing "disableAuthorization yes" for testing. # Or, more simply for just seeing traps: # sudo snmptrapd -f -Lo -Don
    Or use a GUI tool that can receive and display traps.
  3. Test with SNMP Tools:Replace with your device’s IP, with your read-only community (e.g., “public”), and with your read-write community (e.g., “private”).
    • GET Device Status:snmpget -v2c -c <RO_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1.1.0 # Expected Output: enterprises.99999.1.1.1.0 = STRING: "Running"
    • GET Uptime:snmpget -v2c -c <RO_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1.2.0 # Expected Output: enterprises.99999.1.1.2.0 = Timeticks: (xxxx) xx days, x:x:x.xx
    • GET LED State:snmpget -v2c -c <RO_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1.3.0 # Expected Output: enterprises.99999.1.1.3.0 = INTEGER: 0 (or 1 if changed)
    • SET LED State to ON (1):snmpset -v2c -c <RW_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1.3.0 i 1 # Expected Output: enterprises.99999.1.1.3.0 = INTEGER: 1 # Check ESP32 logs for "LED state set to: ON"
    • SET LED State to OFF (0):snmpset -v2c -c <RW_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1.3.0 i 0
    • WALK your custom MIB branch:snmpwalk -v2c -c <RO_Community> <ESP32_IP> .1.3.6.1.4.1.99999.1.1 # Expected Output: Lists all three custom OIDs and their values.
    • Observe Traps:After about 30 seconds (as per the snmp_agent_task loop), you should see a trap message on your snmptrapd console or GUI tool. The ESP32 log will also show “Sending example trap…”.

Tip: OIDs can be tricky. If snmpget or snmpwalk returns “No Such Object” or similar, double-check your OID definitions in custom_mib.c (base OIDs, leaf OIDs, and the tree structure) against the OID you are querying. Remember that scalar OIDs typically end in .0.

Variant Notes

The lwIP SNMP agent is relatively lightweight compared to a full OPC UA server, but still has resource implications:

ESP32 Variant Core(s) Typical SRAM SNMPv2c Agent Suitability Key Considerations
ESP32 (Classic) Dual-Core LX6 520 KB Excellent Ample resources for both the agent and other complex applications. The go-to choice for demanding projects.
ESP32-S3 Dual-Core LX7 512 KB+ Excellent More powerful cores and flexible PSRAM options. Ideal for extensive MIBs or high-frequency polling.
ESP32-S2 Single-Core LX7 320 KB Good Performance is adequate for standard monitoring, but the single core must be shared with Wi-Fi and application logic.
ESP32-C3 Single-Core RISC-V 400 KB Good Capable of running the agent well, but RAM is a key constraint. Best with smaller, optimized MIBs.
ESP32-C6 Single-Core RISC-V 512 KB Very Good Benefits from Wi-Fi 6 and more RAM than C3. Well-suited for reliable SNMP monitoring tasks.
ESP32-H2 Single-Core RISC-V 320 KB Limited Can run a basic agent, but is resource-constrained and designed for 802.15.4. Use only for very simple monitoring.

General Considerations:

  • MIB Complexity: Each MIB object, especially if it involves string storage or complex callback logic, consumes RAM and Flash.
  • Number of OIDs: A very large MIB will consume more resources.
  • Trap Frequency: Sending traps frequently can increase network traffic and CPU usage.
  • SNMPv3: If SNMPv3 support were added (e.g., via a third-party library, as lwIP’s core SNMP is mainly v1/v2c), resource usage (especially for crypto) would increase substantially.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Timeout / No Response Manager tool reports “Timeout” or “No response from remote host”. ESP32 doesn’t log any incoming request.
  1. Check IP: Ensure the manager is using the correct IP address for the ESP32.
  2. Verify Wi-Fi: Confirm the ESP32 is still connected to the Wi-Fi network.
  3. Firewall: Check for firewalls on the manager’s machine or network blocking UDP port 161.
  4. Agent Running: Ensure snmp_init() was called and the task is running.
Incorrect OID Manager returns “No Such Object”, “No Such Instance”, or “Unknown OID”.

Double-check the OID:

  • Ensure the OID queried by the manager exactly matches the definition in your C code.
  • Remember that scalar objects must have a .0 suffix (e.g., .1.3.6…1.1.0).
  • Use snmpwalk on a parent OID (e.g. .1.3.6.1.4.1.99999) to see what the agent actually exposes.
Authentication Failure SET requests fail. GET requests might also fail if the community string is wrong.

Community strings must match perfectly:

  • They are case-sensitive: public is not the same as Public.
  • For SETs, you must use the read-write community string (e.g., private).
  • Verify the strings set in menuconfig or with snmp_set_community().
SET Operation Fails You use the correct read-write community string, but the SET is still rejected.

Check the agent’s logic:

  1. Did you call snmp_set_write_access() in your code?
  2. Add ESP_LOGI to your set_test and set_commit functions to see if they are being called and what values they receive.
  3. The set_test function might be returning an error like SNMP_ERR_WRONGVALUE.
Traps Not Received ESP32 log shows “Sending trap…”, but nothing appears on the manager/trap receiver.

Check the entire path:

  • Destination IP: Is the trap destination IP in menuconfig correct?
  • Receiver Running: Is the trap receiver tool (e.g., snmptrapd) actually running on the destination machine?
  • Firewall (Port 162): Check for firewalls on the receiver’s machine or network blocking the trap port (UDP 162).

Exercises

  1. Add More Custom Scalars:Expand the custom MIB by adding two new read-only scalar objects:
    • esp32FreeHeap (Gauge32): Reports the current free heap memory (esp_get_free_heap_size()). OID: .1.3.6.1.4.1.99999.1.1.4.0
    • esp32MinFreeHeap (Gauge32): Reports the minimum free heap ever recorded (esp_get_minimum_free_heap_size()). OID: .1.3.6.1.4.1.99999.1.1.5.0Implement their get_value callbacks and test them with snmpget.
  2. Implement a Counter:Add a read-only MIB object esp32PacketCount (Counter32) with OID .1.3.6.1.4.1.99999.1.1.6.0. In your main application loop or a periodic task, simulate receiving packets and increment a counter. The SNMP get_value callback for this OID should return the current value of this counter. Test with snmpget.
  3. Conditional Trap:Modify the snmp_agent_task (or create a new periodic task). Simulate monitoring a sensor value (e.g., the g_custom_mib_states.led_state or a new simulated value). If this value crosses a certain threshold (e.g., if led_state is set to 1 by an SNMP SET, consider it an event), send a specific SNMP TRAP. The trap should include at least one varbind indicating the event or the value that crossed the threshold. Test by setting the value via SNMP and observing the trap.

Summary

  • SNMP is a standard protocol for network monitoring and management, involving Managers and Agents.
  • Agents expose manageable data through a Management Information Base (MIB), where objects are identified by Object Identifiers (OIDs).
  • ESP-IDF’s lwIP stack includes an SNMPv1/v2c agent, allowing ESP32 devices to be managed.
  • Custom MIB objects can be defined in C code by providing OIDs, data types, access levels, and callback functions for GET/SET operations.
  • SNMP TRAPs allow the ESP32 agent to send asynchronous notifications about significant events to a manager.
  • Community strings provide basic authentication for SNMPv1/v2c; they should be chosen carefully.
  • Resource constraints on ESP32 variants should be considered when designing the MIB and expecting agent performance.
  • Standard SNMP command-line tools (snmpget, snmpset, snmpwalk, snmptrapd) or GUI MIB browsers are used to interact with the SNMP agent.

Further Reading

  • lwIP SNMP Agent Documentation: Check the lwIP documentation specific to the version included in your ESP-IDF (often found in esp-idf/components/lwip/lwip/doc/snmp.txt or similar within the lwIP source tree).
  • RFC 1157: Simple Network Management Protocol (SNMP) – (SNMPv1)
  • RFC 1213: Management Information Base for Network Management of TCP/IP-based internets: MIB-II
  • RFC 3416: Version 2 of the Protocol Operations for the Simple Network Management Protocol (SNMP) – (Defines SNMPv2c PDUs)
  • RFC 3417: Transport Mappings for the Simple Network Management Protocol (SNMP)
  • Net-SNMP Project: http://www.net-snmp.org/ (Source for tools and further information)
  • ESP-IDF Programming Guide:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top