Luke Pascoe
Date: 2011/8/1
Subject: Re: SNMPd Agent
Script available:
http://sourceforge.net/projects/owfs/files/snmp/1w_snmp.py/download
It uses the SNMPd "pass" feature which passes control of an entire MIB tree
to an external application.
I'm pretty sure the output order could be vastly improved, I'm not that
familiar with SNMP MIBs but looking at the way some of the system output is
done my method is...non standard. I can't think of a better way to handle
the fact that the data is itself a tree though.
Anyway, every item in the OWFS is exported and should be addressed fairly
consistently. I use a cache file to ensure devices keep the same OID
regardless of the order they appear on the filesystem, so as devices are
added and removed the OIDs remain the same. If the contents of the
directories change the OIDs will move about though, but as I understand they
way your stuff works this shouldn't happen except perhaps between version
releases.
This is a first cut, concieved and implemented in a weekend. I'm open to
feedback.
#!/usr/bin/python
import sys
import os
import re
# pass [-p priority] MIBOID PROG
# will pass control of the subtree rooted at MIBOID to the
specified PROG command. GET and GETNEXT requests for OIDs within this tree
# will trigger this command, called as:
#
# PROG -g OID
#
# PROG -n OID
#
# respectively, where OID is the requested OID. The PROG command
should return the response varbind as three separate lines printed to
# stdout - the first line should be the OID of the returned
value, the second should be its TYPE (one of the text strings integer, gauge,
# counter, timeticks, ipaddress, objectid, or string ), and the
third should be the value itself.
#
# If the command cannot return an appropriate varbind - e.g the
specified OID did not correspond to a valid instance for a GET request, or
# there were no following instances for a GETNEXT - then it should
exit without producing any output. This will result in an SNMP
# noSuchName error, or a noSuchInstance exception.
#
# Note: The SMIv2 type counter64 and SNMPv2 noSuchObject
exception are not supported.
#
# A SET request will result in the command being called as:
#
# PROG -s OID TYPE VALUE
#
# where TYPE is one of the tokens listed above, indicating the
type of the value passed as the third parameter.
#
# If the assignment is successful, the PROG command should exit
without producing any output. Errors should be indicated by writing one of
# the strings not-writable, or wrong-type to stdout, and the agent
will generate the appropriate error response.
#
# Note: The other SNMPv2 errors are not supported.
#
# In either case, the command should exit once it has finished
processing. Each request (and each varbind within a single request) will
# trigger a separate invocation of the command.
#
# The default registration priority is 127. This can be changed
by supplying the optional -p flag, with lower priority registrations
# being used in preference to higher priority values.
#========= CONSTANTS
BASE_OID = '.1.3.6.1.4.1.2021.255'
MOUNT = "/var/1wire"
DEVICE_CACHE = '/var/cache/1wire_snmp/devices'
LOGGING = 1
LOGFILE = '/tmp/1wire_snmp.log'
system_dirs = [
'_unused', # 0
'alarm', # 1
'settings', # 2
'simultaneous', # 3
'statistics', # 4
'structure', # 5
'system', # 6
'uncached', # 7
]
system_dirs.extend(['_unused'] * 200)
system_dirs[100] = '_bus'
system_dirs[200] = '_device'
#========= HELPER FUNCTIONS
def usage():
print "Usage: "
sys.exit(2)
def log(f, msg):
if LOGGING:
f.write("%s\n" % msg)
def cmp_oid():
class K(object):
def mycmp(self, x, y):
xx = x.split('.', 1)
yy = y.split('.', 1)
res = cmp(int(xx[0]), int(yy[0]))
if res == 0 and (len(xx) > 1 or len(yy) > 1):
return self.mycmp(xx[1] if len(xx) > 1 else '-1', yy[1]
if len(yy) > 1 else '-1')
return res
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return self.mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return self.mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return self.mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return self.mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return self.mycmp(self.obj, other.obj) >= 0
def __ne__(self, other):
return self.mycmp(self.obj, other.obj) != 0
return K
def full_oid(child):
return "%s.%s" % (BASE_OID, child)
def build_subtree(f, tree, what, base, oid, path):
log(f, "Called build_subtree with: %s" % ((base, oid, path), ))
# No need to look at all if we're getting and we're past the end of oid
if what in ("-g", "-s") and not len(oid):
log(f, " Nothing to do, returning")
return None
dlist = os.listdir("%s/%s" % (MOUNT, path))
for item in dlist:
idx = str(dlist.index(item) + 1)
log(f, " Adding %s.%s %s" % (base, idx, item))
tree["%s.%s" % (base, idx)] = ('string', item)
# No need to go further if we're past the end of the oid
if not len(oid):
log(f, " No need to continue")
break
if len(oid) and oid[0] == str(idx) and os.path.isdir("%s/%s" %
(MOUNT, "%s/%s" % (path, item))):
build_subtree(f, tree, what, "%s.%s" % (base, idx),
oid[1:], "%s/%s" % (path, item))
# We can use a rubbish but quick value if we're only nexting
elif what == '-n' and oid == [idx, '0'] and
os.path.isfile("%s/%s" % (MOUNT, "%s/%s" % (path, item))):
log(f, " Adding %s.%s.0 %s" % (base, idx, 'RUBBISH'))
tree["%s.%s.0" % (base, idx)] = ('string', 'RUBBISH')
# We only want to read the value if we absolutely have to, ie
we're being asked for it specifically
elif ( \
# Are we specifically looking for this value?
(what == '-g' and oid == [idx, '0']) \
# or is it the next oid?
or (what == '-n' and oid == [idx]) \
) \
and os.path.isfile("%s/%s" % (MOUNT, "%s/%s" % (path,
item))):
try:
fvalue = open("%s/%s" % (MOUNT, "%s/%s" %
(path, item)), 'r')
value = fvalue.read().strip()
fvalue.close()
except Exception, e:
log(f, e)
raise e
log(f, " Adding %s.%s.0 %s" % (base, idx, value))
tree["%s.%s.0" % (base, idx)] = ('string', value)
# We get here if ???
else:
pass
#=========== MAIN
# Prep logging
if LOGGING:
f = open(LOGFILE, 'a')
sys.stderr = f
else:
f = None
log(f, "Recv: %s" % repr(sys.argv))
# Read the ID cache
try:
c = open(DEVICE_CACHE, 'r')
cache = [x.strip() for x in c.readlines()]
c.close()
log(f, "Cache: %s" % repr(cache))
except IOError:
cache = []
# Get arguments
try:
WHAT = sys.argv[1]
OID = sys.argv[2]
except IndexError:
usage()
# Exit if it's asking for an tree we don't answer for
if OID[:len(BASE_OID)] != BASE_OID:
sys.exit(3)
OID = OID[len(BASE_OID) + 1:]
SPLIT = OID.split('.')
log(f, "OID: " + OID)
# Check WHAT is valid
if WHAT not in ("-g", "-n", "-s"):
usage()
sys.exit(3)
# Build the base OID tree
tree = {}
cache_altered = False
dlist = os.listdir(MOUNT)
for item in dlist:
if item in system_dirs:
# System folder
idx = str(system_dirs.index(item))
tree[idx] = ('string', item) # Name
# We only have to build deeper for the tree we're being asked
for
if SPLIT[0] == idx:
build_subtree(f, tree, WHAT, idx, SPLIT[1:], item)
elif item[:4] == 'bus.':
# There are busses, add .100
idx1 = str(system_dirs.index('_bus'))
tree[idx1] = (None, 'Busses')
# Bus
idx2 = item[4:]
tree["%s.%s" % (idx1, idx2)] = ("string", item)
# We only have to build deeper for the tree we're being asked
for
if len(SPLIT) >= 2 and SPLIT[0] == idx1 and SPLIT[1] == idx2:
build_subtree(f, tree, WHAT, "%s.%s" % (idx1, idx2),
SPLIT[2:], item)
elif os.path.isdir("%s/%s" % (MOUNT, item)) and
re.match("^\w{2}\.\w{12}$", item):
# There are devices, add .200
idx1 = str(system_dirs.index('_device'))
tree[idx1] = (None, 'Devices')
# Device, check if it's in the cache
if item in cache:
idx2 = str(cache.index(item))
log(f, "Cached device: %s" % item)
else:
# Add it
idx2 = str(len(cache))
cache.append(item)
cache_altered = True
log(f, "New device: %s" % item)
tree["%s.%s" % (idx1, idx2)] = ('string', item)
# We only have to build deeper for the tree we're being asked
for
if len(SPLIT) >= 2 and SPLIT[0] == idx1 and SPLIT[1] == idx2:
build_subtree(f, tree, WHAT, "%s.%s" % (idx1, idx2),
SPLIT[2:], item)
else:
log(f, "Unknown base file '%s', ignoring" % item)
log(f, "Tree: " + repr(tree))
if cache_altered:
# Write the new cache
c = open(DEVICE_CACHE, 'w')
c.writelines(["%s\n" % x.strip() for x in cache])
c.close()
# Answer the query
if WHAT == '-g':
if tree.has_key(OID):
log(f, "Send: %s %s\n" % (full_oid(OID), tree[OID]))
print full_oid(OID)
print tree[OID][0]
print tree[OID][1]
else:
log(f, "No matching OID '%s'\n" % OID)
# Do nothing
elif WHAT == '-n':
# We need a sorted list of keys here
tree_sorted = sorted(tree, key = cmp_oid())
log(f, "Sorted: " + repr(tree_sorted))
i = None
if OID == '':
i = 0
else:
try:
i = tree_sorted.index(OID)
except ValueError:
log(f, "No matching OID '%s'\n" % OID)
sys.exit()
if i < len(tree_sorted) - 1:
i += 1
else:
log(f, "No next OID '%s'\n" % OID)
sys.exit()
log(f, "Index: %s" % (tree[tree_sorted[i]], ))
next_oid = None
for branch in tree_sorted[i:]:
if tree[branch][0]:
next_oid = branch
break
log(f, "Discarding branch: %s" % branch)
if next_oid:
log(f, "Send: %s %s\n" % (full_oid(next_oid), tree[next_oid]))
print full_oid(next_oid)
print tree[next_oid][0]
print tree[next_oid][1]
else:
log(f, "No next OID '%s'\n" % OID)
sys.exit()
elif WHAT == '-s':
try:
TYPE = sys.argv[3]
VALUE = sys.argv[4]
except IndexError:
usage()
# DOING NOTHING YET
else:
usage()
if LOGGING:
log(f, '')
f.close()
------------------------------------------------------------------------------
Got Input? Slashdot Needs You.
Take our quick survey online. Come on, we don't ask for help often.
Plus, you'll get a chance to win $100 to spend on ThinkGeek.
http://p.sf.net/sfu/slashdot-survey
_______________________________________________
Owfs-developers mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/owfs-developers