Merge branch 'feature/daemon' into develop
This commit is contained in:
commit
96ddb976b5
5
APKBUILD
5
APKBUILD
@ -1,13 +1,13 @@
|
||||
# Contributor: Paolo Asperti <paolo@asperti.com>
|
||||
# Maintainer: Paolo Asperti <paolo@asperti.com>
|
||||
pkgname=openpdu
|
||||
pkgver=0.1.2
|
||||
pkgver=0.2.0
|
||||
pkgrel=1
|
||||
pkgdesc="OpenPDU project - main binary"
|
||||
url="https://github.com/openpdu/openpdu"
|
||||
arch="noarch"
|
||||
license="GPL2"
|
||||
depends="python py-argh apk-cron i2c-tools"
|
||||
depends="python py-argh apk-cron py-bottle i2c-tools"
|
||||
makedepends=""
|
||||
install="openpdu.post-install"
|
||||
subpackages=""
|
||||
@ -25,4 +25,5 @@ package() {
|
||||
install -Dm755 etc/local.d/openpdu.start "$pkgdir"/etc/local.d/openpdu.start
|
||||
|
||||
install -Dm755 openpdu "$pkgdir"/usr/bin/openpdu
|
||||
install -Dm755 openpdud "$pkgdir"/usr/bin/openpdud
|
||||
}
|
||||
|
@ -18,6 +18,13 @@
|
||||
# EXAMPLE I2C BOARD
|
||||
# [board1]
|
||||
# type = i2c-out
|
||||
# address = 20
|
||||
# address = 0x27
|
||||
# channels = 8
|
||||
# bus = 1
|
||||
# bus = 0
|
||||
|
||||
# EXAMPLE I2C CURRENT BOARD
|
||||
# [board2]
|
||||
# type = i2c-current
|
||||
# address = 0x48
|
||||
# channels = 4
|
||||
# bus = 0
|
||||
|
@ -20,6 +20,8 @@ startpower = 1
|
||||
# [outlet3]
|
||||
# board=1
|
||||
# channel=1
|
||||
# currentboard=2
|
||||
# currentboardchannel=1
|
||||
# description = MailServer
|
||||
|
||||
# [outlet4]
|
||||
|
475
openpdud
Executable file
475
openpdud
Executable 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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user