Musson Industrial's Embr-SNMP Module (BETA)

There's no way to divert a key-value pair to a separate resource file, outside of config.json.

The short term roadmap is to supercede system.device.addDevice (and a whole bunch of other functions) with a single system.config namespace that, broadly, exposes C/R/U/D capabilities for all types of gateway config via a single scripting API. That namespace will include the ability to do the things Phil's concerned about with addDevice.

2 Likes

Sure, but we don't have anything special in that regard either.

But as Paul is saying... basically all these existing scripting functions for adding devices or connections or editing some random property are all headed for deprecation. Would have been nice to have it all done for 8.3.0 but :man_shrugging:

:smiley:

Fair enough.

2 Likes

I am hoping to develop some autodiscovery tools to add to this, my only major roadblock is I have never written in Kotlin, and bmusson wrote this whole thing in Kotlin. I am on my way to learning.

3 Likes

I’m open to PRs in Java, I can handle the conversion if you provide the ā€œbusiness logicā€.

4 Likes

While off topic somewhat, I'd like to start tinkering with writing a module for Ignition. I'm a computer science major, but they weren't teaching Java back when I went to school, and haven't done much coding besides writing PLC logic and the scripting in Ignition in many many years. Where would be a good place to start? I have seen Kotlin, Gradle, and Maven thrown around but haven't even started looking at what those are and how they relate to Java. (I haven't programmed anything really major since VB6 was popular....LOL). My next problem though is finding the time to even learn it....been busy with back to back to back projects lately.

1 Like

I majored in Chemical Engineering and minored in Paper Science lol. As the famous line from Ratatouille says: ā€œAnyone Can Cookā€.

My learning strategy is to just FAAFO.

Read and try to understand an existing module, compile it yourself, make some minor changes, then recompile and prove that you can make something happen.

There’s enough good examples of drivers, script modules, components, etc. that you should be able to find a starting point for whatever you want to accomplish.

Kotlin vs Java? If you don’t know either, but you know Python, Kotlin will probably feel more comfortable. But personally I think there’s great value in learning Java before moving onto Kotlin.

Gradle vs Maven? Don’t worry about it until it matters. Build tools are just a means to an end. I think the simplest way to get started building/signing modules is with IA’s Gradle plugin, so probably just use that until you have a reason not to.

7 Likes

I have never been formally trained in any code, never went to university, and am fully self taught.
I had a time a number of months back where I decided to try Java to get into modules, what I found valuable was to take another module I liked that was open source, and started modifying it.
I broke a lot of things, then learnt how to fix them. I still find a lot of things challenging to understand, but then the little things started to make more sense. Also it meant that I could get instant gratification from making a small change to a working code base that then could be fully compiled and run.
The language itself is less of an obstacle than learning the right way to structure a code base, and how to read API docs and extract the information you need.
Also, especially in this forum, there are some very generous and helpful people that seem to really enjoy watching people learning to extend themselves. These people have helped me a lot.

5 Likes

Thanks to both the Norcal-SNMP and Embr-SNMP teams for their efforts to make their SNMP drivers work. I have tested both successfully (read only, v2c) in the office with some of our rapid deployment field equipment where SNMP is the only monitoring we’ve got (and very limited at that). The field failure risk is relatively high (esp for power) due to the rapid, limited, compromised and temporary nature of the deployments so hopefully we can monitor, alarm and intervene before failure. It will also enable to log data on the equipment’s performance over time to optimise specs.

Best regards,
Patrick.

2 Likes

Just a heads up, I found a potential bug:

I installed the most recent release (0.3.0) on a Windows server and am testing with a couple of Moxa branded devices using the SNMPv3 driver. I am monitoring the NAT table of the device so I can better document our OT network. I set up a UDT to have access to much more rows than I would need, so that if there was a change I would be able to see it. IE if a row gets added to the table, the additional tags would auto-populate with the values of the NAT table.

When the NAT table gets a new row added, the new values don’t get read in. The values stay with a quality of:

Bad("Bad_NotFound: A requested item was not found or a search operation ended without success.")

This stays until I access the device with an external SNMP tool and walk the table. At that point, the values display in Ignition.

Restarting the module does not fix this issue.

1 Like

Interesting. I think this points towards this being a Moxa related problem.

Currently reading from the SNMP Agent is handled naively:

  1. For all the tags scheduled in a group, create the largest GET PDU’s possible.
  2. Send the PDU’s.
  3. If any OIDs in the PDU are rejected, remove them and try again.

This keeps happening until all the bindings have been read or marked as failed.

Notably:

  1. OIDs are only ā€œblacklistedā€ for that current scheduled read; the driver does not maintain a list of rejected OIDs.
  2. The driver only used GET messages, never GETNEXT or GETBULK (which is the more ā€œnormalā€ way to read entire tables).

So it sounds the Moxa isn’t updating its static (GET-able) address space until it receives a dynamic request (GETNEXT/GETBULK).

Eventually I’d like to add proper table support, which would make the driver behave more like your external SNMP tool.

@bmusson Apologies if I’ve overlooked the documentation, but what is the device polling rate? It would be great to have the ability to configure this, in the same way we can set the health check frequency.

Currently, each item is polled at the subscription’s sampling interval (requests for items with the same subscription rate are batched together).

For OPC tags, this is the rate specified for the tag group.

2 Likes

Just playing with the beta version on GH, thought I would share a simple script to demo some of the useful functions we can achieve with it.

Gather MAC addresses connected to the ports on a switch
def get_mac_port_mapping(device):
    """
    Get MAC addresses and their associated ports from a switch
    """
    
    # OIDs
    MAC_TABLE_OID = '1.3.6.1.2.1.17.4.3.1.1'      # dot1dTpFdbAddress
    PORT_TABLE_OID = '1.3.6.1.2.1.17.4.3.1.2'     # dot1dTpFdbPort
    BRIDGE_PORT_OID = '1.3.6.1.2.1.17.1.4.1.2'    # dot1dBasePortIfIndex
    IF_NAME_OID = '1.3.6.1.2.1.31.1.1.1.1'        # ifName
    
    # Get MAC addresses
    mac_table = system.snmp.agent.walk(device, [MAC_TABLE_OID])
    
    # Get bridge port numbers for each MAC
    port_table = system.snmp.agent.walk(device, [PORT_TABLE_OID])
    
    # Get bridge port to ifIndex mapping
    bridge_port = system.snmp.agent.walk(device, [BRIDGE_PORT_OID])
    
    # Get interface names
    if_names = system.snmp.agent.walk(device, [IF_NAME_OID])
    
    # Build reverse lookup for bridge port to ifIndex
    bridge_to_if = {}
    for row in range(bridge_port.size()):
    	oid = str(bridge_port[row].oid)
    	ifIndex = bridge_port[row].value
        bridge_port_num = oid.split('.')[-1]
        bridge_to_if[bridge_port_num] = str(ifIndex)
    
    # Build interface name lookup
    if_name_lookup = {}
    for row in range(if_names.size()):
    	oid = str(if_names[row].oid)
    	name = if_names[row].value
        ifindex = oid.split('.')[-1]
        if_name_lookup[ifindex] = str(name)

    mac_port_list = []
    
    for row in range(mac_table.size()):
    	mac_oid = str(mac_table[row].oid)
    	mac_value = mac_table[row].value
        # Extract the MAC address index from OID
        mac_index = '.'.join(mac_oid.split('.')[-6:])
        port_oid = PORT_TABLE_OID + '.' + mac_index
        
        for item in range(port_table.size()):
        	if str(port_table[item].oid) == port_oid:
				bridge_port_num = str(port_table[item].value)
	        	
				# Get interface index and name
				ifindex = bridge_to_if.get(bridge_port_num, 'Unknown')
				if_name = if_name_lookup.get(ifindex, 'Unknown')
	            
				mac_formatted = mac_value
				
				mac_port_list.append({
				    'mac': mac_formatted,
				    'bridge_port': bridge_port_num,
				    'ifindex': ifindex,
				    'interface': if_name
				})
    return mac_port_list
3 Likes

Release

This release adds scripting and expression functions for interacting with SNMP Agents.
I'm still considering this module a beta until some existing bugs get fixed.

NOTE: The 8.1 version has only been minorly tested. Any feedback would be appreciated.

Downloads

Embr v8.3 - 2026.1.21
Embr v8.1 - 2026.1.21

Changelog

Minor Changes

Add scripting functions:

  • system.snmp.agent.readAsync(agent, oids)
  • system.snmp.agent.readBlocking(agent, oids)
  • system.snmp.agent.writeAsync(agent, oids, values)
  • system.snmp.agent.writeBlocking(agent, oids, values)
  • system.snmp.agent.walkAsync(agent, oids)
  • system.snmp.agent.walkBlocking(agent, oids)
  • system.snmp.agent.readTableAsync(agent, columns, lowerBoundIndex, upperBoundIndex)
  • system.snmp.agent.readTableBlocking(agent, columns, lowerBoundIndex, upperBoundIndex)
  • systen.snmp.agent.getAgent(agent)
    • This returns an agent proxy that supports read(Async/Blocking), write(Async/Blocking), walk(Async/Blocking), and readTable(Async/Blocking).

Add expression functions:

  • snmpRead(agent, oid1, [oid2, ...])
  • snmpReadTable(agent, column1, [column2, ...])
  • snmpWalk(agent, oid1, [oid2, ...])

Support

GitHub Sponsors
Issues that need resolved before a v1.0.0 release.

5 Likes

New beta: Embr-SNMP-Ignition83-0.4.0.modl (964.6 KB)

This adds a whole bunch of stuff.

Agent Device Improvements

  • Upon startup, the driver will walk the entirety of an agent's objects tree to learn the model.
    • This model is kept up to date as new reads are performed.
  • Upon startup, the driver will attempt to learn the maximum supported request/response sizes of the agent.
    • This should resolve the "PDU Too Big" errors when reading long strings.
    • This should help improve performance, but I still want to test this.
  • Value writes are now properly coerced into the correct underlying SNMP datatypes.

OPC UA model improvements:

  • Added an Objects folder that supports browsing all known objects.
    • Only numeric OIDs are supported (still).
  • Object nodes are modeled using a new set of SNMP specific DataTypes.
  • Object nodes currently support several properties:
    • {oid}/DataType returns the underlying SNMP DataType of the object as a string.
    • {oid}/ExpectedSize returns the expected size of the variable, which is used for PDU creation. This one is meant for troubleshooting.
  • Added ReadTable and Walk OPC UA methods.
    • These return arrays of OidValue, a { oid: Oid, value: BaseDataType } structure.
  • Added more diagnostics nodes.

Feedback

  • Can you subscribe to all the objects in your agent? What happens if you try?
4 Likes

I'll have to update my version at home and see if this lets me pull all the data from my 48 port Cisco switch without all the separate tag groups. If so, this is a HUGE update that will put this driver over the top.

4 Likes