diff --git a/pyghmi/ipmi/oem/lenovo/energy.py b/pyghmi/ipmi/oem/lenovo/energy.py new file mode 100644 index 00000000..a86d1924 --- /dev/null +++ b/pyghmi/ipmi/oem/lenovo/energy.py @@ -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) diff --git a/pyghmi/ipmi/oem/lenovo/handler.py b/pyghmi/ipmi/oem/lenovo/handler.py index fefede6d..c7b52e86 100755 --- a/pyghmi/ipmi/oem/lenovo/handler.py +++ b/pyghmi/ipmi/oem/lenovo/handler.py @@ -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): diff --git a/pyghmi/ipmi/oem/lenovo/imm.py b/pyghmi/ipmi/oem/lenovo/imm.py index 5d107058..2f2e4783 100644 --- a/pyghmi/ipmi/oem/lenovo/imm.py +++ b/pyghmi/ipmi/oem/lenovo/imm.py @@ -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: