2
0
mirror of https://opendev.org/x/pyghmi synced 2025-01-27 19:37:44 +00:00

Add support for Lenovo Energy Meters

Energy meters are available through OEM commands for Lenovo enterprise
servers.  This enables those data to appear as sensors when enumerated.

Change-Id: I28d59b24cf49642e82d5b71f7c34f94d858796dd
This commit is contained in:
Jarrod Johnson 2017-10-13 16:08:32 -04:00
parent 9131df4ced
commit 243cf797de
3 changed files with 119 additions and 1 deletions

View File

@ -0,0 +1,81 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pyghmi.exceptions as pygexc
import struct
class EnergyManager(object):
def __init__(self, ipmicmd):
# there are two IANA possible for the command set, start with
# the Lenovo, then fallback to IBM
# We start with a 'find firmware instance' to test the water and
# get the handle (which has always been the same, but just in case
self.iana = bytearray('\x66\x4a\x00')
try:
rsp = ipmicmd.xraw_command(netfn=0x2e, command=0x82,
data=self.iana + '\x00\x00\x01')
except pygexc.IpmiException as ie:
if ie.ipmicode == 193: # try again with IBM IANA
self.iana = bytearray('\x4d\x4f\x00')
rsp = ipmicmd.xraw_command(netfn=0x2e, command=0x82,
data=self.iana + '\x00\x00\x01')
else:
raise
if rsp['data'][4:6] not in (b'\x02\x01', b'\x02\x06', b'\x02\x09'):
raise pygexc.UnsupportedFunctionality(
"Energy Control {0}.{1} not recognized".format(rsp['data'][4],
rsp['data'][5]))
self.modhandle = bytearray(rsp['data'][6:7])
if self.get_ac_energy(ipmicmd):
self.supportedmeters = ('AC Energy', 'DC Energy')
else:
self.supportedmeters = ('DC Energy',)
def get_energy_precision(self, ipmicmd):
rsp = ipmicmd.xraw_command(
netfn=0x2e, command=0x81,
data=self.iana + self.modhandle + b'\x01\x80')
print(repr(rsp['data'][:]))
def get_ac_energy(self, ipmicmd):
try:
rsp = ipmicmd.xraw_command(
netfn=0x2e, command=0x81,
data=self.iana + self.modhandle + b'\x01\x82\x01\x08')
# data is in millijoules, convert to the more recognizable kWh
return float(
struct.unpack('!Q', rsp['data'][3:])[0]) / 1000000 / 3600
except pygexc.IpmiException as ie:
if ie.ipmicode == 0xcb:
return 0.0
raise
def get_dc_energy(self, ipmicmd):
rsp = ipmicmd.xraw_command(
netfn=0x2e, command=0x81,
data=self.iana + self.modhandle + b'\x01\x82\x00\x08')
# data is in millijoules, convert to the more recognizable kWh
return float(struct.unpack('!Q', rsp['data'][3:])[0]) / 1000000 / 3600
if __name__ == '__main__':
import os
import pyghmi.ipmi.command as cmd
import sys
c = cmd.Command(sys.argv[1], os.environ['BMCUSER'], os.environ['BMCPASS'])
EnergyManager(c).get_dc_energy(c)

View File

@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2016 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -355,16 +355,25 @@ class OEMHandler(generic.OEMHandler):
for name in nextscale.get_sensor_names(self._fpc_variant):
yield nextscale.get_sensor_reading(name, self.ipmicmd,
self._fpc_variant)
elif self.has_imm:
for name in self.immhandler.get_oem_sensor_names(self.ipmicmd):
yield self.immhandler.get_oem_sensor_reading(name,
self.ipmicmd)
def get_sensor_descriptions(self):
if self.is_fpc:
return nextscale.get_sensor_descriptions(self._fpc_variant)
elif self.has_imm:
return self.immhandler.get_oem_sensor_descriptions(self.ipmicmd)
return ()
def get_sensor_reading(self, sensorname):
if self.is_fpc:
return nextscale.get_sensor_reading(sensorname, self.ipmicmd,
self._fpc_variant)
elif self.has_imm:
return self.immhandler.get_oem_sensor_reading(sensorname,
self.ipmicmd)
return ()
def get_inventory_of_component(self, component):

View File

@ -17,9 +17,12 @@
from datetime import datetime
import json
import os.path
import pyghmi.constants as pygconst
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.oem.lenovo.energy as energy
import pyghmi.ipmi.private.session as ipmisession
import pyghmi.ipmi.private.util as util
import pyghmi.ipmi.sdr as sdr
import pyghmi.util.webclient as webclient
import random
import struct
@ -70,6 +73,7 @@ class IMMClient(object):
self.username = ipmicmd.ipmi_session.userid
self.password = ipmicmd.ipmi_session.password
self._wc = None # The webclient shall be initiated on demand
self._energymanager = None
self.datacache = {}
@staticmethod
@ -290,6 +294,30 @@ class IMMClient(object):
except KeyError:
return None
def get_oem_sensor_names(self, ipmicmd):
if self._energymanager is None:
self._energymanager = energy.EnergyManager(ipmicmd)
return self._energymanager.supportedmeters
def get_oem_sensor_descriptions(self, ipmicmd):
return [{'name': x, 'type': 'Power'
} for x in self.get_oem_sensor_names(ipmicmd)]
def get_oem_sensor_reading(self, name, ipmicmd):
if self._energymanager is None:
self._energymanager = energy.EnergyManager(ipmicmd)
if name == 'AC Energy':
kwh = self._energymanager.get_ac_energy(ipmicmd)
elif name == 'DC Energy':
kwh = self._energymanager.get_dc_energy(ipmicmd)
else:
raise pygexc.UnsupportedFunctionality('No sunch sensor ' + name)
return sdr.SensorReading({'name': name, 'imprecision': None,
'value': kwh, 'states': [],
'state_ids': [],
'health': pygconst.Health.Ok,
'type': 'Power'}, 'kWh')
def weblogout(self):
if self._wc:
try: