NetBurner 3.5.7
PDF Version
SNMP Table Read

Example Path: examples/snmp/TableRead

SNMP Table Read Example

Overview

This example demonstrates a read-only SNMP table MIB on a NetBurner device. Eight simulated sensors are registered with the SNMP agent at startup, and a background task in UserMain writes fresh readings into the per-row data structs every few seconds. SNMP GET / GET-NEXT / walk requests pick up the latest values automatically; nothing about the SNMP tree is ever re-registered after startup.

Use this pattern when you have repeating measured data (one row per sensor, per RS-485 board, per port) that an external manager only ever needs to read. Read-only access is enforced simply by what we never register: the code uses AddSnmpEntry to expose each column for reading, and never calls AddSnmpWriteEntry.

Why a Table Instead of N Scalars

The CustomMIB example shows how to wire up individual scalars with SNMPREADFUNC(name, "oid.literal", type, fn, mask). That macro is fine for true singletons – read community, write community, trap destination – but it does not scale:

  • Each SNMPREADFUNC bakes the OID into the macro as a string literal, so each variable needs its own line.
  • The read-handler signature is parameterless (snmp_typeXxx ReadFunc()), with no void * context telling the function which OID is being read. A single function therefore cannot serve many OIDs, ruling out data-driven registration.

For a device polling, say, 12 RS-485 boards x 12 parameters each (~150 OIDs), the macro form forces 150+ hand-written functions and macro lines – repetitive and impossible to drive from data.

The SNMP table is the standard idiom for "N instances of the same columns". The NetBurner runtime exposes the underlying registration APIs that the macro wraps:

API Purpose
AddSnmpEntry(subid, prefix, len, suffix, fn, data_el, m) Register one column of one row
AddSnmpWriteEntry(...) Register a writable column
SnmpRemoveTableElement(prefix, data_el) Remove a row at runtime

The handler signatures carry the context the macro form lacks:

void PutTableElements(SNMP_Request &req, void *data_el, int subid);
int WrtTE_xxx(snmp_typeXxx var, int bTest, void *pElement);
  • data_el – opaque pointer to your row data (e.g. one board's struct)
  • subid – column number within the row

    One handler serves every OID in the table.** Registering N rows is a simple loop; updating values is just writing into a struct.

When to Keep SNMPREADFUNC

Keep the scalar macros for true singletons that have no natural index:

  • Read community
  • Write community
  • Trap destination
  • One-off configuration scalars

Use a table for any repeating shape (rows of sensors, ports, boards, sessions, connections). This example uses one read-only scalar (sensorTableNumber – the row count) plus a full sensor table to illustrate both styles side by side.

Features

  • SNMP agent listening on UDP port 161 for queries
  • A static SNMP table under 1.3.6.1.4.1.8174.20.1.* with eight read-only columns (Index, Name, Temperature, Humidity, Pressure, Uptime, ReadCount, Status), populated with 8 simulated sensors at startup
  • Read-only scalar sensorTableNumber (1.3.6.1.4.1.8174.19.0) reporting the row count
  • Background polling task in UserMain that updates the per-row struct fields every 5 seconds (simulating an RS-485 poll loop)
  • SNMP enterprise trap with a custom variable binding
  • Interactive command processor over Telnet (port 23) and serial
  • HTTP web UI on port 80 – live sensor dashboard with rows colored by sensorStatus, plus a Send-Test-Trap button. Auto-refreshes every 5 seconds (matches the polling cadence)
  • User authentication for command sessions

Network Ports

Port 23 (TCP) - Telnet command interface
Port 80 (TCP) - HTTP web server
Port 161 (UDP) - SNMP agent (inbound queries)
Port 162 (UDP) - SNMP trap destination (outbound)

MIB Structure

Defined in mib/NburnSensorTable_Mib.txt and implemented in src/sensor.cpp.

Scalar:

OID Name Type Access
1.3.6.1.4.1.8174.19.0 sensorTableNumber INTEGER Read-Only

Table (under 1.3.6.1.4.1.8174.20):

sensorTable . sensorEntry . column . index
1.3.6.1.4.1.8174.20 . 1 . 1..8 . <sensorIndex>

Columns within sensorEntry:

1 sensorIndex (INTEGER, index)
2 sensorName (DisplayString)
3 sensorTemperature (INTEGER, hundredths of degrees C)
4 sensorHumidity (INTEGER, percent)
5 sensorPressure (Gauge, hundredths of kPa)
6 sensorUptime (TimeTicks)
7 sensorReadCount (Counter, polls observed for this row)
8 sensorStatus (INTEGER, 1 = ok, 2 = warn, 3 = fault)

Build Instructions

SNMP support is enabled by the predef-overload.h file located at:

overload/nbrtos/include/predef-overload.h

which contains:

#define ENABLE_SNMP (1)

Project Files

makefile - Build configuration
src/main.cpp - Globals (AppName, SYSDESC,
SYSOID, TheSnmpServer) plus a
UserMain that brings up the
stack, calls InitSensors() and
StartCommandLine(), then runs
the 5-second poll loop
src/commands.h
src/commands.cpp - Telnet/serial CLI -- five
file-scope Process* callbacks
plus the public
StartCommandLine() that wires
them up and starts the
command-processor task
src/trap.h
src/trap.cpp - SNMP trap PDU builder +
SendTestTrap(). Called from
the 'T' command in commands.cpp
src/sensor.h
src/sensor.cpp - SensorData rows, per-row
registration
(AddTableElementsensorEntry),
single read dispatcher
(PutTableElementssensorEntry),
plus public InitSensors(),
PollSensors(), and
WriteSensorTableRows() (used
by the web UI)
src/web.cpp - HTTP UI: CPPCALL handlers for
the live sensor table and
trap-status note, plus the
Send-Trap form's POST callback
src/htmlvar.h - Header included by the
generated src/htmldata.cpp;
declares (or pulls in via
<nbrtos.h>) every symbol
referenced by a
<!--VARIABLE ... --> tag in
html/
html/index.html - Web UI page. Auto-refreshes
every 5 seconds, embeds
<!--CPPCALL ShowSensorRows -->
which delegates to
WriteSensorTableRows() in
sensor.cpp
mib/NburnSensorTable_Mib.txt - SMI MIB definition
overload/nbrtos/include/
predef-overload.h - Build flag to enable SNMP
const char * AppName
EFFS Multiple Partitions Example.
Definition aes/src/main.cpp:10
int read(int fd, char *buf, int nbytes)
Read data from a file descriptor (fd).
const char * SYSDESC
Application-defined system description string for MIB-II sysDescr.
Definition snmp/CustomMIB/src/main.cpp:44
const char * SYSOID
Application-defined system object identifier string for MIB-II sysObjectID.
Definition snmp/CustomMIB/src/main.cpp:45

How the Read-Only Table Code Works

The table is built around three pieces in src/sensor.cpp, exposed via src/sensor.h:

  1. AddTableElementsensorEntry(void *data_el, snmp_typeINTEGER index) For one row, calls AddSnmpEntry(...) once per column. The same opaque data_el pointer (the SensorData* for that row) is handed to the dispatcher on every callback. No AddSnmpWriteEntry calls appear anywhere – read-only enforcement comes from what is never registered. This function is file-scope; the public entry is InitSensors() below.
  2. PutTableElementssensorEntry(SNMP_Request &req, void *data_el, int subid) The single read dispatcher. data_el identifies the row; subid (1..8) identifies the column. It writes the right ASN.1 type into req.put_asn for that field. Also file-scope.
  3. InitSensors() and PollSensors() (declared in sensor.h) InitSensors runs once from UserMain and registers all 8 rows. PollSensors runs every 5 seconds inside UserMain's idle loop and just writes fresh values into Sensors[i].*. No SNMP plumbing is touched after InitSensors returns.

This is the model recommended for systems that poll N similar data sources: build the tree once, then keep updating the row structs.

The CLI ('D' to dump the SNMP tree, 'T' to send a trap, logout to end the session) lives in src/commands.cpp, exposed through a single public function StartCommandLine() in src/commands.h. The trap PDU itself is built in src/trap.cpp; commands.cpp includes trap.h and calls SendTestTrap() when the user types 'T'.

The web UI in html/index.html and src/web.cpp is a different view of the same data: a single HTML page with a <meta http-equiv="refresh" content="5"> tag so the browser re-fetches every 5 seconds (matching the device's polling cadence). The page contains:

  • A status bar showing system uptime via <!--VARIABLE Secs -->.
  • The 8x8 sensor table, rendered by <!--CPPCALL ShowSensorRows -->. The handler in web.cpp delegates to WriteSensorTableRows() in sensor.cpp; that function iterates Sensors[] and writes one <tr class="ok|warn|fault">...</tr> per row. CSS rules in the page color the rows green / yellow / red based on the class.
  • A <form action="trap" method="post"> whose submit button fires SendTestTrap() – the same call the 'T' CLI command uses. The POST callback in web.cpp redirects back to /index.html on completion and a counter shows how many traps have been sent.

Encapsulation: the SensorData struct stays private to sensor.cpp; web.cpp only sees the WriteSensorTableRows(int sock) declaration in sensor.h. This keeps the row-data layout free to evolve without touching the web layer.

Commands

Connect via serial console or Telnet to port 23. Log in with any username and password that are not identical (e.g., user: "hello", pass: "world").

D - Dump the full SNMP MIB tree to the console
T - Send a test SNMP enterprise trap
logout - Close the current session
(other) - Display command help

Testing with the net-snmp Command-Line Tools

The NetBurner SDK ships a portable net-snmp 5.9.1 distribution at:

C:\nburn\snmp\net-snmp\

containing snmpget.exe, snmpset.exe, snmpwalk.exe, snmptable.exe, snmptranslate.exe, etc. The standard MIBs (RFC1155-SMI, RFC1213-MIB, SNMPv2-SMI, SNMPv2-TC, ...) live alongside the per-example MIBs at:

C:\nburn\snmp\mibs\

If you copy this example's mib\NburnSensorTable_Mib.txt into that mibs folder, the tools will resolve symbolic names like sensorTableNumber.0, sensorTemperature.5, sensorPressure.7, etc. from NBURNSENSOR-MIB. Without the copy you can still use full numeric OIDs and everything works.

All examples below use the example IP address 192.168.1.100. Substitute your NetBurner's actual IP (run nbdiscovery if you do not know it).

One-time setup so you can copy/paste cleanly

Windows cmd:

set PATH=C:\nburn\snmp\net-snmp;PATH% set MIBDIRS=C:\nburn\snmp\mibs set MIBS=ALL

PowerShell:

$env:PATH = "C:\nburn\snmp\net-snmp;$env:PATH" $env:MIBDIRS = "C:\nburn\snmp\mibs" $env:MIBS = "ALL"

Bash (Git Bash / MSYS):

export PATH="/c/nburn/snmp/net-snmp:$PATH" export MIBDIRS="/c/nburn/snmp/mibs" export MIBS="ALL"

After that the -M / -m flags below can be omitted. All examples include them explicitly so they work even without the env vars.

snmpget – read individual cells

The row count scalar:

snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorTableNumber.0

Expected output:

NBURNSENSOR-MIBsensorTableNumber.0 = INTEGER: 8

Pick specific cells (column.row syntax):

snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorName.3 snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorTemperature.5 sensorPressure.7 snmpget -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorStatus.0 sensorReadCount.0 sensorUptime.0

Numeric form (no MIB needed):

snmpget -v2c -c public 192.168.1.100 .1.3.6.1.4.1.8174.19.0 snmpget -v2c -c public 192.168.1.100 .1.3.6.1.4.1.8174.20.1.2.3

snmpwalk – enumerate the table

Walk the entire sensor table (8 columns x 8 rows = 64 OIDs):

snmpwalk -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorTable

Walk both the scalar and the table:

snmpwalk -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 .1.3.6.1.4.1.8174

Walk the table twice ~10 seconds apart and observe that sensorTemperature, sensorReadCount, and sensorUptime change between walks – that is the polling task updating the row structs.

snmptable – show the table as a grid

Much easier to read than a column-major snmpwalk:

snmptable -v2c -c public -M C:\nburn\snmp\mibs -m ALL -Cb 192.168.1.100 sensorTable

Expected output (values jitter between polls):

SNMP table: NBURNSENSOR-MIBsensorTable

Index Name Temperature Humidity Pressure Uptime ReadCount Status 0 "Sensor #0" 2230 41 10146 0:0:00:28.65 5 1 1 "Sensor #1" 2281 42 10150 0:0:00:28.65 5 1 2 "Sensor #2" 2300 43 10154 0:0:00:28.65 5 1 3 "Sensor #3" 2351 44 10158 0:0:00:28.65 5 1 ...

snmpset – must fail on this read-only table

Every column is read-only. A SET on any cell must be rejected:

snmpset -v2c -c public -M C:\nburn\snmp\mibs -m ALL 192.168.1.100 sensorTemperature.0 i 9999

Expected response:

Error in packet. Reason: notWritable (That object does not support modification) Failed object: NBURNSENSOR-MIBsensorTemperature.0

Read-only enforcement comes from what sensor.cpp never registers – there are AddSnmpEntry() calls but no AddSnmpWriteEntry() calls.

snmptranslate – offline OID name/number lookup

snmptranslate -M C:\nburn\snmp\mibs -m ALL -On sensorTableNumber.0 snmptranslate -M C:\nburn\snmp\mibs -m ALL .1.3.6.1.4.1.8174.20.1.3.0

Troubleshooting

Cannot find module (NBURNSENSOR-MIB) Copy this example's mib\NburnSensorTable_Mib.txt to C:\nburn\snmp\mibs\ and retry.

Timeout Verify with ping 192.168.1.100 and confirm the device is on the same subnet. SNMP listens on UDP/161; check Windows Firewall.

Error in packet, Reason: notWritable You attempted to SET a read-only column. Every column in this table is read-only by design.

Same values returned every poll The polling loop updates the row structs every 5 seconds. If sensorReadCount and sensorUptime never increment, check that UserMain reached the while(1) { OSTimeDly(...); PollSensors(); } loop.

SNMP System Identifiers

sysDescr.0 : "NetBurner SNMP Table Read example" sysObjectID.0 : 1.3.6.1.4.1.8174.2.41