Agentuino - A lightweight SNMP Agent

Not certain how we are supposed to port old subject and posted chains. So a new one has been created in continuation of the Agentuino Library.

The original community interactions can be found here; http://arduino.cc/forum/index.php/topic,38103.msg282272.html#msg282272

Project source code is hosted on Google and can be located here; http://code.google.com/p/agentuino/

Welcome to the new forum all.

For those of you have been using the library I found bug and offering a correction that will prevent the MCU from hanging with an unknown request.

As per included sample sketch with the library, please see modifications, as per included code:

void pduReceived()
{
  SNMP_PDU pdu;
  //
  #ifdef DEBUG
    Serial << F("UDP Packet Received Start..") << F(" RAM:") << freeMemory() << endl;
  #endif
  //
  api_status = Agentuino.requestPdu(&pdu);
  //
  if ( pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET
    && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) {
    //
    pdu.OID.toString(oid);
    //
    Serial << "OID: " << oid << endl;
    //
    if ( strcmp_P(oid, sysDescr ) == 0 ) {
	// handle sysDescr (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read-only
	  pdu.VALUE.encode(SNMP_SYNTAX_NULL);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = SNMP_ERR_READ_ONLY;
	} else {
	  // response packet from get-request - locDescr
	  status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locDescr);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("sysDescr...") << locDescr << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else if ( strcmp_P(oid, sysUpTime ) == 0 ) {
	// handle sysName (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read-only
	  pdu.VALUE.encode(SNMP_SYNTAX_NULL);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = SNMP_ERR_READ_ONLY;
	} else {
	  // response packet from get-request - locUpTime
	  status = pdu.VALUE.encode(SNMP_SYNTAX_TIME_TICKS, locUpTime);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("sysUpTime...") << locUpTime << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else if ( strcmp_P(oid, sysName ) == 0 ) {
	// handle sysName (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read/write
	  status = pdu.VALUE.decode(locName, strlen(locName));
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	} else {
	  // response packet from get-request - locName
	  status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locName);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("sysName...") << locName << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else if ( strcmp_P(oid, sysContact ) == 0 ) {
	// handle sysContact (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read/write
	  status = pdu.VALUE.decode(locContact, strlen(locContact));
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	} else {
	  // response packet from get-request - locContact
	  status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locContact);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("sysContact...") << locContact << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else if ( strcmp_P(oid, sysLocation ) == 0 ) {
	// handle sysLocation (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read/write
	  status = pdu.VALUE.decode(locLocation, strlen(locLocation));
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	} else {
	  // response packet from get-request - locLocation
	  status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locLocation);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("sysLocation...") << locLocation << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else if ( strcmp_P(oid, sysServices) == 0 ) {
	// handle sysServices (set/get) requests
	if ( pdu.type == SNMP_PDU_SET ) {
	  // response packet from set-request - object is read-only
	  pdu.VALUE.encode(SNMP_SYNTAX_NULL);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = SNMP_ERR_READ_ONLY;
	} else {
	  // response packet from get-request - locServices
	  status = pdu.VALUE.encode(SNMP_SYNTAX_INT, locServices);
	  pdu.type = SNMP_PDU_RESPONSE;
	  pdu.error = status;
	}
	//
	#ifdef DEBUG
	  Serial << F("locServices...") << locServices << F(" ") << pdu.VALUE.size << endl;
	#endif
    } else {
	// oid does not exist
	//
	// response packet - object not found
	pdu.VALUE.encode(SNMP_SYNTAX_NULL);
	pdu.type = SNMP_PDU_RESPONSE;
	pdu.error = SNMP_ERR_NO_SUCH_NAME;
    }
    //
    Agentuino.responsePdu(&pdu);
  }
  //
  Agentuino.freePdu(&pdu);
  //
  Serial << "UDP Packet Received End.." << " RAM:" << freeMemory() << endl;
}

By adding “pdu.VALUE.encode(SNMP_SYNTAX_NULL);” it will send a response to the Manager either way and prevent the MCU from hanging.

More changes are planned and required in order to support Get-Next functionality, so stay tuned for improvements. Again, developers are welcome to contribute, please contact me if your interested.

OK, that makes sense in case a request comes in with an unknown OID.

However, my problem was that the request was apparently not an SNMP request at all. So the following if clause from your sample was not true:

// check if valid packet
  if ( (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET) 
        && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) 
    {
    //

I worked around it by adding an else clause to that if and send an error response back anyway.

else if ( strcmp_P(oid, DewPoint ) == 0 ) // DewPoint request
    {
      // handle sysName (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) 
      {
        // response packet from set-request - object is read-only
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = SNMP_ERR_READ_ONLY;
      } 
      else 
      {
        // response packet from get-request - DewPoint
        status = pdu.VALUE.encode(SNMP_SYNTAX_INT32, v_DewPoint);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
      Serial << "v_DewPoint..." << v_DewPoint << " " << pdu.VALUE.size << endl;
    } 


// add more custom OID blocks here


    
    else 
    {
      // oid does not exist
      //
      // response packet - object not found
      pdu.VALUE.encode(SNMP_SYNTAX_NULL);
      pdu.type = SNMP_PDU_RESPONSE;
      pdu.error = SNMP_ERR_NO_SUCH_NAME;
      Serial << "Unknown OID" << endl;
    }
    
    //
    Serial << "Sending response..." << endl;
    
    Agentuino.responsePdu(&pdu);
    count++;


// close bracket for the 'if' in the first code block above
  }
  

  else 
  // packet not valid, send GENERAL_ERROR response. Required, otherwise the invalid packet 
  // will get stuck in the buffer and processed over and over again
  
  {
    Serial << "Unknown Packet!!" << endl;
    Serial << "PDU Type: " << pdu.type << " PDU Error: " << pdu.error << " API status: "<< api_status << endl; 
    pdu.type = SNMP_PDU_RESPONSE;
    pdu.error = SNMP_ERR_GEN_ERROR;
    Agentuino.responsePdu(&pdu);
    Serial << "Sent 'GENERAL_ERROR' response" << endl;
  }

  //
  Serial << "freeing PDU.." << " RAM:" << freeMemory() << endl;
  Agentuino.freePdu(&pdu);
  //
  Serial << "UDP Packet Received End.." << " RAM:" << freeMemory() << endl;

} // end pduReceived()

thanks for sharing yesyes.

I have to add that I have no idea just how wrong it is to reply this way. But it works for me as long as no new SNMP request comes in before the previous one has been replied to…

yesyes the short answer is no.

We should probably clear the incoming buffer and send nothing back if it isn't a valid SNMP packet. The problem with clearing the buffer is if another UDP protocol library is used in conjunction with the SNMP library. I'll need to look into better handling of invalid packets but at least the "quick fix" you suggested seems to work.

Our alpha release works fairly well considering it's footprint size (Flash/RAM) and the amount of time put into it based on the testing I've done to date. I really want to get the get-next requests working and the more I dive into implementing this functionality, more will change with the library.

No posts to this in a while?

I'd like to add the capability to set the community strings via the Ethernet shield web forms on these active posts:

http://arduino.cc/forum/index.php/topic,54324.0.html

http://arduino.cc/forum/index.php/topic,55044.0.html

Take a look at the Agentuino constructors as there is one that allows you to set the community names. This would allow you to declare public variables within your sketch that could be updated by a web form. You may want to store them in EEPROM as well or any new settings will be lost on reboot.

You may want to look at a Webduino library; multi web page/form implementation is easier per say.

Eric

Why I'm getting errors from the example?

AgentuinoClass agent = Agentuino();

SNMP_SESSION session;

Both are not okay...

Steve, which example are you referring to? I don't see these lines in the example I have (Agent.pde)

#include <Agentuino.h>

#include <Agentuino.h>

/**
* Agentuino SNMP Agent Library Prototyping...
*
* Copyright 2010 Eric C. Gionet <lavco_eg@hotmail.com>
*
*/
#include <Streaming.h>         // Include the Streaming library
#include <Ethernet.h>          // Include the Ethernet library
#include <MemoryFree.h>
#include <Agentuino.h>
#include <avr/pgmspace.h>
//
static byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
static byte ip[] = { 192, 168, 2, 64 };
static byte gateway[] = { 192, 168, 1, 190 };
static byte subnet[] = { 255, 255, 255, 0 };
//
//
// RFC1213-MIB OIDs
static char sysDescr[] PROGMEM      = "1.3.6.1.2.1.1.1.0";  // read-only  DisplayString
static char sysObjectID[] PROGMEM   = "1.3.6.1.2.1.1.2.0";  // read-only  (ObjectIdentifier)
static char sysUpTime[] PROGMEM     = "1.3.6.1.2.1.1.3.0";  // read-only  (TimeTicks)
static char sysContact[] PROGMEM    = "1.3.6.1.2.1.1.4.0";  // read-write (DisplayString)
static char sysName[] PROGMEM       = "1.3.6.1.2.1.1.5.0";  // read-write (DisplayString)
static char sysLocation[] PROGMEM   = "1.3.6.1.2.1.1.6.0";  // read-write (DisplayString)
static char sysServices[] PROGMEM   = "1.3.6.1.2.1.1.7.0";  // read-only  (Integer)
//
//
/* RFC1213 local values */
static char locDescr[] PROGMEM      = { "Agentuino, a light-weight SNMP Agent." };  // read-only (static)
static char locObjectID[] PROGMEM   = "1.3.6.1.3.2009.0";                       // read-only (static)
static long locUpTime               = 0;                                        // RTC is needed for this unless the NTP Time library is used
static char locContact[20]          = "Eric Gionet";
static char locName[20]             = "Agentuino";
static char locLocation[20]         = "Nova Scotia, CA";
static short locServices PROGMEM    = 7;                                        // read-only (static)

AgentuinoClass agent = Agentuino();

SNMP_SESSION session;


void pduReceived()
{
  char oidString[SNMP_MAX_OID_LEN];
  SNMP_PDU pdu;
  //
  Serial << "UDP Packet Received Start.." << " RAM:" << freeMemory() << endl;
  //
  agent.requestPdu(&pdu);
  //
  pdu.OID.toString(oidString);
  //
  Serial << "OID: " << oidString << endl;
  //
  if ( pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT ) {
    //
    if ( strcmp_P(oidString, sysName) == 0 ) {
      //
      // response packet - locName
      pdu.type = SNMP_PDU_RESPONSE;
      pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locName);
      //
      Serial << "sysName..." << locName << " " << pdu.VALUE.size << endl;
      //
      agent.responsePdu(&pdu);
    } else {
      // oid does not exist
      //
      // response packet - object not found
    }
  }
  //
  agent.freePdu(&pdu);
}

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  //
  session.getCommName = "public";
  session.setCommName = "private";
  session.port = 161;
  //
  agent.initSession(&session);
  agent.onPduReceive(pduReceived);
  //
  delay(10);
  //
  Serial.println("Initalized...");
}

void loop()
{
  agent.listen();
}

Please take a look at the library's class constructor to see how to use the session struct. Based on your posted code your missing some parameters.

    // set community name set/get sizes

    session->setSize = strlen(session->setCommName);

    session->getSize = strlen(session->getCommName);

    //

    // validate get/set community name sizes

    if ( session->setSize > SNMP_MAX_NAME_LEN || session->getSize > SNMP_MAX_NAME_LEN ) {

        return SNMP_API_STAT_NAME_TOO_BIG;

    }

    //

    // set session property

    _session = session;

    //

    // validate session port number

    if ( session->port == NULL || session->port == 0 ) session->port = SNMP_DEFAULT_PORT;

    //

    // init UDP socket

    _socket.initUDP(session->port);

    //

    return SNMP_API_STAT_SUCCESS;

I’m afraid I need a little more help with the SNMP library and possibly the underlying Ethernet library…

It’s been a while but I’ve now come back to troubleshooting / debugging my weather station sketch. The problem is that occasionally a received packet gets stuck and is being processes over and over again. I’m not sure what that packet is but it’s definitely not an SNMP packet.

What I have done is I added lots and lost of serial port output to the sketch and I keep a machine running to log that output to a file. I have attached both the sketch and the part of the log file that shows the sketch running into the problem.

The problem starts at line 2170 in the log.txt. What is happening is that (for some reason) the Arduino receives an unknown packet from the same IP that usually sends the SNMP GET requests. Since it is not a valid SNMP packet, the following if statement is not true (from line 200 in the sketch):

  // check if valid packet
  if ( (pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET) 
        && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) 
    {

So the sketch jumps to the following else statement (line 505)

  else 
  // packet not valid, send GENERAL_ERROR response. Required, otherwise the invalid packet 
  // will get stuck in the buffer and processed over and over again
  
  {
    Serial << "Unknown Packet!!" << endl;
    Serial << "PDU Type: " << pdu.type << " PDU Error: " << pdu.error << " API status: "<< api_status << endl; 
    Serial << "from IP: " << Agentuino.g_dstIP(0) << "." << Agentuino.g_dstIP(1) << "." << Agentuino.g_dstIP(2) << "." << Agentuino.g_dstIP(3) << endl;   
    pdu.type = SNMP_PDU_RESPONSE;
    pdu.error = SNMP_ERR_GEN_ERROR;
//    Agentuino.responsePdu(&pdu);
    Serial << "Sent 'GENERAL_ERROR' response" << endl;
  }

So far so good. However, the next time and all subsequent times I call Agentuino.listen() that same packet is still in the buffer. No further received packets are being processed.

The question is, why does this packet stay in the buffer? Is there a way to flush the buffer from my sketch (or from Agentuino) if an unknown packet is being received? I’ve been through all the components of the Ethernet library (Udp, Ethernet, W5100, socket) and also the Agentuino library to figure out where and how the packet buffer is released but I couldn’t find it.

BTW, the method Agentuino.g_dstIP() is one I added to the Agentuino library in order to get the IP address where the packet came from in an attempt to determine what the nature of the offending packets is.

Weather_Station.pde (20.4 KB)

log.txt (51.4 KB)

Hmmm, nobody? I was hoping LAVco would read this... ;-)

Yesyes,

I been using your Weather Station code as a basis to create a simple test using a TMP36 temperature sensor connected to analog0. For the most part everything works well. However, there are two problems: 1. The device does not respond if I connect the power (wall wart or usb). Only after pressing reset does everything start working. 2. After a few hours the device hangs up and the SNMP stops responding. I can still ping the device but no SNMP until I do a reset.

By comparing the first Weather Station code that I found initially in a previous post and the code in the post above I saw that you added a watchdog timer to the setup routine. I don't know what a watchdog timer is but I added it and will be testing for stability. Any tips would be greatly appreciated.

Also, I noticed that this line is commented out // Agentuino.responsePdu(&pdu);

Have had any luck troubleshooting your issues?

The Agentuino library seems to be such a useful tool if it could be polished just a little more. I wonder if anyone would work on it for a bounty?

  1. The device does not respond if I connect the power (wall wart or usb). Only after pressing reset does everything start working.

Yes, I noticed that too. It seems the Ethernet shield doesn't bring the link up on first power on, only after a reset. No idea if there is a way to fix that. I would be rather interested in a solution to this.

  1. After a few hours the device hangs up and the SNMP stops responding. I can still ping the device but no SNMP until I do a reset.

This sounds like the issue I was describing in my previous post. There seems to be a non-SNMP packet stuck in the buffer that gets processed over and over again. Still looking for a solution but I have a workaround using a watchdog timer (see below).

By comparing the first Weather Station code that I found initially in a previous post and the code in the post above I saw that you added a watchdog timer to the setup routine. I don't know what a watchdog timer is but I added it and will be testing for stability. Any tips would be greatly appreciated.

That relates to a hardware watchdog timer I built. It's based on a NE555 timer (but of course! :D ). It triggers a reset pulse if it did not get a timer pulse for 3 seconds. Let me know if you want the circuit diagram. In the loop() function I pulse the watchdog timer input once every loop if bool reset is false. Then, when a non-SNMP packet is received, I set bool reset to true. This causes the loop not to pulse the watchdog and 3 seconds later the Arduino resets. Bit of a dirty workaround but it works for now.

Also, I noticed that this line is commented out // Agentuino.responsePdu(&pdu);

I take it that is the line in the SNMP function that handles non-SNMP packets? I did put that in at some point hoping that replying to the non-SNMP packet would get it out of the buffer. It didn't work, so I commented it out (but leaving it there, just in case).

Have had any luck troubleshooting your issues?

Nothing more that what is mentioned above. Too many other projects going on at the moment. The weather station works as it is now, so finding better fixes has low priority. ;-)

The Agentuino library seems to be such a useful tool if it could be polished just a little more. I wonder if anyone would work on it for a bounty?

Agreed. Though I'm not sure whether the issues are in the Agentuino library or the underlying Ethernet and UDP libraries.. I wonder if LAVco is still working on it?

It would be great if you could send me your 555 watchdog circuit. BTW, thank you so much for sharing your code. Your example really helped.

OK, will post the circuit when I get home (still at work now).

I’m glad my code helped you… :wink:

OK, here you go. Watchdog circuit attached.

Any questions - just ask. :wink:

watchdog (Schematic).pdf (8.97 KB)

Thanks for the circuit diagram! Currently the system has been up for more than 5 hours with just the code changes. I'll monitor it over the weekend and see how it behaves.