Merge branch 'feature/daemon' into develop

This commit is contained in:
Paolo Asperti 2018-09-15 14:50:10 +02:00
commit 96ddb976b5
4 changed files with 489 additions and 4 deletions

View File

@ -1,13 +1,13 @@
# Contributor: Paolo Asperti <paolo@asperti.com> # Contributor: Paolo Asperti <paolo@asperti.com>
# Maintainer: Paolo Asperti <paolo@asperti.com> # Maintainer: Paolo Asperti <paolo@asperti.com>
pkgname=openpdu pkgname=openpdu
pkgver=0.1.2 pkgver=0.2.0
pkgrel=1 pkgrel=1
pkgdesc="OpenPDU project - main binary" pkgdesc="OpenPDU project - main binary"
url="https://github.com/openpdu/openpdu" url="https://github.com/openpdu/openpdu"
arch="noarch" arch="noarch"
license="GPL2" license="GPL2"
depends="python py-argh apk-cron i2c-tools" depends="python py-argh apk-cron py-bottle i2c-tools"
makedepends="" makedepends=""
install="openpdu.post-install" install="openpdu.post-install"
subpackages="" subpackages=""
@ -25,4 +25,5 @@ package() {
install -Dm755 etc/local.d/openpdu.start "$pkgdir"/etc/local.d/openpdu.start install -Dm755 etc/local.d/openpdu.start "$pkgdir"/etc/local.d/openpdu.start
install -Dm755 openpdu "$pkgdir"/usr/bin/openpdu install -Dm755 openpdu "$pkgdir"/usr/bin/openpdu
install -Dm755 openpdud "$pkgdir"/usr/bin/openpdud
} }

View File

@ -18,6 +18,13 @@
# EXAMPLE I2C BOARD # EXAMPLE I2C BOARD
# [board1] # [board1]
# type = i2c-out # type = i2c-out
# address = 20 # address = 0x27
# channels = 8 # channels = 8
# bus = 1 # bus = 0
# EXAMPLE I2C CURRENT BOARD
# [board2]
# type = i2c-current
# address = 0x48
# channels = 4
# bus = 0

View File

@ -20,6 +20,8 @@ startpower = 1
# [outlet3] # [outlet3]
# board=1 # board=1
# channel=1 # channel=1
# currentboard=2
# currentboardchannel=1
# description = MailServer # description = MailServer
# [outlet4] # [outlet4]

475
openpdud Executable file
View File

@ -0,0 +1,475 @@
#!/usr/bin/env python
import os
import sys
import argh
#from argh import arg
import re
import glob
import time
import ConfigParser
import json as JSON
from bottle import route, run, template
import smbus2
#import math
from numpy import mean, sqrt, square
VERSION = '0.1.2'
boardsDefaults = {'inverted':False}
outletsDefaults = {'description': 'Generic outlet','startpower':False}
_boards = []
_outlets = []
@route('/version')
def index():
return VERSION
@route('/boards')
def index():
b = [board.toJSON() for board in _boards]
return JSON.dumps({'boards':b})
@route('/initboards')
def index():
for board in _boards:
board.init()
return
@route('/initoutlets')
def index():
for outlet in _outlets:
outlet.init()
return
@route('/outlets')
def index():
o = [outlet.toJSON() for outlet in _outlets]
return JSON.dumps({'outlets':o})
@route('/outlet/<outlet>/power/<onoff>')
def index(outlet, onoff):
outlet=int(outlet)
o = [o for o in _outlets if o.outletnum==outlet]
if len(o) != 1:
msg = 'wrong outlet number: %s' % str(outlet)
return JSON.dumps({'message':msg})
theOutlet = o[0]
status = (onoff == 'on')
out = theOutlet.setpower(status)
if out is None:
msg = "Cannot set power status for outlet %s" % outlet
return JSON.dumps({'message':msg,'outlet':outlet})
else:
pwrstr = 'on' if out==1 else 'off'
msg = "Outlet #%s powered %s" % (outlet, pwrstr)
return JSON.dumps({'powerstatus':out==1,'outlet':outlet})
@route('/outlet/<outlet>/power')
def index(outlet):
outlet=int(outlet)
o = [o for o in _outlets if o.outletnum==outlet]
if len(o) != 1:
msg = 'wrong outlet number: %s' % str(outlet)
return JSON.dumps({'message':msg})
theOutlet = o[0]
out = theOutlet.getpower()
return JSON.dumps({'powerstatus':out,'outlet':outlet})
@route('/outlet/<outlet>/current')
def index(outlet):
outlet=int(outlet)
o = [o for o in _outlets if o.outletnum==outlet]
if len(o) != 1:
msg = 'wrong outlet number: %s' % str(outlet)
return JSON.dumps({'message':msg})
theOutlet = o[0]
out = theOutlet.getcurrent()
return JSON.dumps({'current':out,'outlet':outlet})
class BoardDummy(object):
channels = []
def __init__(self, boardnum, channels=None, filename=None):
self.boardnum = boardnum
if channels is None:
self.channels = 1
else:
self.channels = int(channels)
self.parser = ConfigParser.SafeConfigParser()
self.filename = filename
if os.path.isfile(filename) and os.access(filename, os.R_OK):
pass
else:
with open(self.filename, 'wb') as theFile:
self.parser.add_section('STATUS')
for c in range(0,self.channels):
self.parser.set('STATUS', 'channel%s' % c, '0')
self.parser.write(theFile)
def toJSON(self):
return {'boardnum':self.boardnum,'type':'dummy','channels':self.channels}
def setpower(self, channel, power):
self.parser.read(self.filename)
p = '1' if power else '0'
s = self.parser.set('STATUS', 'channel%s' % channel, p)
with open(self.filename, 'wb') as theFile:
return self.parser.write(theFile)
return False
def getpower(self, channel):
self.parser.read(self.filename)
s = self.parser.get('STATUS', 'channel%s' % channel)
return int(s)==1
def init(self):
pass
# MCP23008
class BoardI2COut(object):
data = 0
next_refresh = 0
lifetime_sec = 2
def __init__(self, boardnum, channels=None, address=None, bus=None, inverted=False):
self.boardnum = boardnum
if channels is None:
self.channels = 0
else:
self.channels = channels
if address is None:
self.address = 0x20
else:
self.address = int(address, 16)
if bus is None:
self.bus = 1
else:
self.bus = bus
self.inverted = inverted
if not glob.glob('/dev/i2c*'):
raise OSError('Cannot access I2C. Please ensure I2C is enabled')
self._bus = smbus2.SMBus(self.bus)
def toJSON(self):
return {'boardnum':self.boardnum,'type':'i2c-out','channels':self.channels,'address':self.address}
def setpower(self, channel, power):
old_data = data = self.getdata()
mask = 1 << channel
data &= ~mask
if self.inverted:
power = not power
if power:
data |= mask
if old_data != data:
self.next_refresh = 0
return self._bus.write_byte_data(self.address, 0x09,data)
def getpower(self, channel):
data = self.getdata()
d = ( data >> channel ) & 1
c = 0 if self.inverted else 1
return d == c
def getdata(self):
now = time.time()
if now > self.next_refresh:
self.data = self._bus.read_byte_data(self.address, 0x0A)
self.next_refresh = now + self.lifetime_sec
return self.data
def init(self):
return self._bus.write_byte_data(self.address, 0x00, 0x00)
class BoardGpioOut(object):
gpios = []
def __init__(self, boardnum, channels=None, gpios=None, inverted=False):
self.boardnum = boardnum
if channels is None:
self.channels = 0
else:
self.channels = int(channels)
self.inverted = inverted
if not isinstance(gpios, list):
print 'No gpios specified for gpio board %s' % self.boardnum
if len(gpios) != self.channels:
print 'Wrong number of gpios specified for gpio board %s' % self.boardnum
self.gpios = [int(gpio) for gpio in gpios]
def toJSON(self):
return {'boardnum':self.boardnum,'type':'gpio-out','channels':self.channels,'inverted':self.inverted}
def setpower(self, channel, power):
io = self.gpios[channel]
fn = '/sys/class/gpio/gpio%s/value' % io
f = open(fn,'w')
if self.inverted:
power = not power
out = '1' if power else '0'
return f.write(out)
def getpower(self, channel):
io = self.gpios[channel]
fn = '/sys/class/gpio/gpio%s/value' % io
f = open(fn,'r')
e = f.read()
power = int('0'+e) == 1
if self.inverted:
power = not power
return power
def init(self):
for gpio in self.gpios:
if not os.path.isdir('/sys/class/gpio/gpio%s/' % gpio):
open('/sys/class/gpio/export','w').write(str(gpio))
fn = '/sys/class/gpio/gpio%s/direction' % gpio
open(fn,'w').write('out')
return
class Outlet(object):
def __init__(self, outletnum, boardnum, channel, startpower=False):
self.outletnum = int(outletnum)
b = [b for b in _boards if b.boardnum==int(boardnum)]
self.board = b[0]
self.channel = int(channel)
self.description = 'Outlet # %s' % self.outletnum
self.startpower = startpower
self.currentboard = None
self.currentboardchannel = None
def init(self):
self.setpower(self.startpower)
return
def setpower(self, power):
self.board.setpower(self.channel,power)
if self.board.getpower(self.channel) == power:
return power
else:
return None
def getpower(self):
return self.board.getpower(self.channel)
def getcurrent(self):
if self.currentboard is None:
return 0
return self.currentboard.readcurrent(self.currentboardchannel)
def setcurrentboard(self, boardnum, channel):
b = [b for b in _boards if b.boardnum==int(boardnum)]
self.currentboard = b[0]
self.currentboardchannel = channel
def toJSON(self):
status = self.board.getpower(self.channel)
return {'outlet':self.outletnum,'description':self.description,'board':self.board.boardnum,'channel':self.channel,'powerstatus':status}
class BoardI2Ccurrent(object):
data = 0
next_refresh = 0
lifetime_sec = 2
def __init__(self, boardnum, channels=None, address=None, bus=None):
self.boardnum = boardnum
if channels is None:
self.channels = 0
else:
self.channels = channels
if address is None:
self.address = 0x48
else:
self.address = int(address, 16)
if bus is None:
self.bus = 1
else:
self.bus = bus
if not glob.glob('/dev/i2c*'):
raise OSError('Cannot access I2C. Please ensure I2C is enabled')
self._bus = smbus2.SMBus(self.bus)
self.my_zero = 19920
self.num_samples = 100
def toJSON(self):
return {'boardnum':self.boardnum,'type':'i2c-current','channels':self.channels,'address':self.address}
def _readvalue(self, channel):
# 0 = no effect / 1 = start a single conversion
cfg_OS = 1
# multiplexer configurations:
cfg_chan = 0b100
if channel == 0:
# AIN0 -> GND
cfg_chan = 0b100
elif channel == 1:
# AIN1 -> GND
cfg_chan = 0b101
elif channel == 2:
# AIN2 -> GND
cfg_chan = 0b110
elif channel == 3:
# AIN3 -> GND
cfg_chan = 0b111
# unused multiplexer configurations:
# 0b000 AIN0 -> AIN1
# 0b001 AIN0 -> AIN3
# 0b010 AIN1 -> AIN3
# 0b011 AIN2 -> AIN3
# gain amplifier configuration
# 000 = +- 6.144 V
# 001 = +- 4,096 V
# 010 = +- 2,048 V
# 011 = +- 1,024 V
# 100 = +- 0,512 V
# 101 = +- 0,256 V
# 110 = +- 0,256 V
# 111 = +- 0,256 V
cfg_amplifier = 0b000
# 0 = continuous conversion / 1 = single-shot or power-down
cfg_mode = 1
# data rate
# 000 = 128 sps
# 001 = 250 sps
# 010 = 490 sps
# 011 = 920 sps
# 100 = 1600 sps
# 101 = 2400 sps
# 110 = 3300 sps
# 111 = 3300 sps
cfg_sps = 0b110
# 0 = traditional comparator / 1 = window comparator
cfg_comp = 0
# 0 = comparator active low / 1 = comparator active high
cfg_comp_pol = 0
# 0 = comparator non-latching / 1 = comparator latching
cfg_comp_latch = 0
# comparator queue and disable
# 00 = assert after one conversion
# 01 = assert after two conversions
# 10 = assert after three conversions
# 11 = disable comparator
cfg_comp_queue = 0b11
config = cfg_sps << 13 | cfg_comp << 12 | cfg_comp_pol << 11 | cfg_comp_latch << 10 | cfg_comp_queue << 8 | cfg_OS << 7 | cfg_chan << 4 | cfg_amplifier << 1 | cfg_mode
self._bus.write_word_data(self.address, 0x01, config)
while True:
d1 = self._bus.read_word_data(self.address, 0x01)
if d1 == config:
break
d1 = self._bus.read_word_data(self.address, 0x00)
msb = d1 & 0x00FF
lsb = d1 & 0xFF00
val = msb << 8 | lsb >> 8
return (val - self.my_zero) >> 4
def readcurrent(self, channel):
samples = []
for i in range(1,self.num_samples):
v = self._readvalue(channel)
samples.append(v)
rms = sqrt(mean(square(samples)))
return rms
boardsConfigParser = ConfigParser.SafeConfigParser(defaults=boardsDefaults)
boardsConfigFile = '/etc/openpdu/boards.conf'
if os.path.isfile(boardsConfigFile) and os.access(boardsConfigFile, os.R_OK):
boardsConfigParser.read(boardsConfigFile)
for s in boardsConfigParser.sections():
if re.match('^board.*',s):
bType = boardsConfigParser.get(s, 'type')
num = int(re.sub(r'^board','',s))
inverted = int('0' + boardsConfigParser.get(s, 'inverted')) == 1
channels = int(boardsConfigParser.get(s, 'channels'))
if bType=='gpio-out':
gpios = []
for g in range(0,channels):
gpios.append(int(boardsConfigParser.get(s, 'channel%s' % g)))
b = BoardGpioOut(boardnum=num, channels=channels, gpios=gpios, inverted=inverted)
_boards.append(b)
elif bType=='i2c-out':
address = boardsConfigParser.get(s, 'address')
bus = boardsConfigParser.get(s, 'bus')
b = BoardI2COut(boardnum=num, channels=channels, address=address, bus=bus, inverted=inverted)
_boards.append(b)
elif bType=='i2c-current':
address = boardsConfigParser.get(s, 'address')
bus = boardsConfigParser.get(s, 'bus')
b = BoardI2Ccurrent(boardnum=num, channels=channels, address=address, bus=bus)
_boards.append(b)
elif bType=='dummy':
filename = boardsConfigParser.get(s, 'filename')
b = BoardDummy(boardnum=num, channels=channels, filename=filename)
_boards.append(b)
outletsConfigParser = ConfigParser.SafeConfigParser(defaults=outletsDefaults)
outletsConfig = '/etc/openpdu/outlets.conf'
if os.path.isfile(outletsConfig) and os.access(outletsConfig, os.R_OK):
outletsConfigParser.read(outletsConfig)
for s in outletsConfigParser.sections():
if re.match('^outlet.*',s):
num = int(re.sub(r'^outlet','',s))
description = outletsConfigParser.get(s, 'description')
boardnum = outletsConfigParser.get(s, 'board')
channel = outletsConfigParser.get(s, 'channel')
startpower = int('0' + outletsConfigParser.get(s, 'startpower')) == 1
o = Outlet(outletnum=num, boardnum=boardnum, channel=channel, startpower=startpower)
o.description = description
if outletsConfigParser.has_option(s,'currentboard') and outletsConfigParser.has_option(s,'currentboardchannel'):
currentboard = outletsConfigParser.get(s, 'currentboard')
currentboardchannel = int(outletsConfigParser.get(s, 'currentboardchannel'))
o.setcurrentboard(currentboard, currentboardchannel)
_outlets.append(o)
for sec in range(1,100):
for outlet in range(0,4):
o = [o for o in _outlets if o.outletnum==outlet]
if len(o) != 1:
msg = 'wrong outlet number: %s' % str(outlet)
print JSON.dumps({'message':msg})
theOutlet = o[0]
out = theOutlet.getcurrent()
print JSON.dumps({'current':out,'outlet':outlet})
print "---"
#run(host='0.0.0.0', port=5000)