%%%============================================================================= %%% Copyright (c) 2012-2019 Lindenbaum GmbH %%% %%% Permission to use, copy, modify, and/or distribute this software for any %%% purpose with or without fee is hereby granted, provided that the above %%% copyright notice and this permission notice appear in all copies. %%% %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %%% %%% @doc %%% A module providing reading and decoding functionality for Sensor Data %%% Records (SDRs). %%% @end %%%============================================================================= -module(eipmi_sdr). -export([get_repository/1, maybe_get_repository/2, get/2, get_sensor_reading/3, get_sensor_reading/2, convert/2, to_list/1]). -include("eipmi.hrl"). -define(MAX_READ_COUNT, 16). -define(READ, {?IPMI_NETFN_STORAGE_REQUEST, ?GET_SDR}). -define(GET_INFO, {?IPMI_NETFN_STORAGE_REQUEST, ?GET_SDR_REPOSITORY_INFO}). -define(RESERVE, {?IPMI_NETFN_STORAGE_REQUEST, ?RESERVE_SDR_REPOSITORY}). -define(GET_READING, {?IPMI_NETFN_SENSOR_EVENT_REQUEST, ?GET_SENSOR_READING}). -type record_type() :: full | compact | event_only | entity_association | device_relative_entity_association | generic_device_locator | fru_device_locator | management_controller_device_locator | management_controller_confirmation | bmc_message_channel_info | oem | reserved. -type property() :: eipmi_sensor:addr() | {record_id, non_neg_integer()} | {sdr_version, string()} | {record_type, record_type()} | {record_length, non_neg_integer()} | {nominal_reading, non_neg_integer()} | {nominal_maximum, non_neg_integer()} | {nominal_minimum, non_neg_integer()} | {maximum_reading, non_neg_integer()} | {minimum_reading, non_neg_integer()} | {container, [eipmi_sensor:entity()]} | {containee, [eipmi_sensor:entity() | eipmi_sensor:addr()]} | {linked_records_exist, boolean()} | {presence_sensor_always_accessible, boolean()} | {access_addr, non_neg_integer()} | {device_type, non_neg_integer()} | {device_type_modifier, non_neg_integer()} | {id, string()} | {device_support, [atom()]} | {firmware_version, string()} | {ipmi_version, string()} | {manufacturer_id, non_neg_integer()} | {product_id, non_neg_integer()} | {guid, string()} | {msg_channel_info, [{transmit_support, boolean()} | {sensor_lun, non_neg_integer()} | {protocol, ipmb | {icmb, number()} | {sm_bus, number()} | system_format | {oem, number()}}]} | {messaging_interrupt, {irq, non_neg_integer()} | {pci, string()} | smi | sci | {interrupt, non_neg_integer()} | assigned} | {event_message_buffer_interrupt, {irq, non_neg_integer()} | {pci, string()} | smi | sci | {interrupt, non_neg_integer()} | assigned} | {data, binary()} | {percentage, boolean()} | {sensor_format, unsigned | ones_complement | twos_complement} | {sensor_rate, eipmi_sensor:unit() | {eipmi_sensor:unit(), eipmi_sensor:unit()} | {eipmi_sensor:unit(), per, eipmi_sensor:unit()}} | {sensor_unit, eipmi_sensor:unit()} | {sensor_tolerance, {number(), eipmi_sensor:unit()}} | {sensor_resolution, {number(), eipmi_sensor:unit()}} | {sensor_accuracy, {number(), percent}} | {sensor_coefficients, {linear | ln | log10 | log2 | e | exp10 | exp2 | '1/x' | sqr | cube | sqrt | 'cube-1' | non_linear | oem_non_linear, integer(), integer(), integer(), integer()}} | {sensor_number, non_neg_integer()} | {sensor_numbers, [non_neg_integer()]} | {sensor_type, eipmi_sensor:type()} | {reading_type, eipmi_sensor:reading()} | {sensor_direction, input | output}. -type entry() :: {record_type(), [property()]} | {sdr_info, [{version, string()} | {entries, non_neg_integer()} | {free_space, non_neg_integer()} | {most_recent_addition, non_neg_integer()} | {most_recent_erase, non_neg_integer()} | {overflow, boolean()} | {operations, [delete | partial_add | reserve | get_allocation_info]}]}. -type reading() :: eipmi_sensor:value() | {sensor_reading, {number(), eipmi_sensor:unit()}} | {sensor_threshold, atom()}. -export_type([record_type/0, property/0, entry/0, reading/0]). %%%============================================================================= %%% API %%%============================================================================= %%------------------------------------------------------------------------------ %% @doc %% Read all entries from the sensor data record repository (SDR). %% @end %%------------------------------------------------------------------------------ -spec get_repository(pid()) -> [entry()]. get_repository(SessionPid) -> maybe_get_repository(SessionPid, []). %%------------------------------------------------------------------------------ %% @doc %% Read all entries from the sensor data record repository (SDR) *only* when %% the SDR repository changed since the last read. %% @end %%------------------------------------------------------------------------------ -spec maybe_get_repository(pid(), [entry()]) -> [entry()]. maybe_get_repository(SessionPid, OldSdrRepository) -> {ok, SdrInfo} = eipmi_session:rpc(SessionPid, ?GET_INFO, []), OldSdrInfo = proplists:get_value(sdr_info, OldSdrRepository, []), LastRecentActionTimestamp = get_recent_action_timestamp(OldSdrInfo), CurrentRecentActionTimestamp = get_recent_action_timestamp(SdrInfo), case CurrentRecentActionTimestamp > LastRecentActionTimestamp of true -> Reserve = needs_reservation(SdrInfo), Entries = proplists:get_value(entries, SdrInfo), maybe_get_repository(Entries, SessionPid, Reserve, SdrInfo); false -> OldSdrRepository end. maybe_get_repository(0, _SessionPid, _Reserve, SdrInfo) -> [{sdr_info, SdrInfo}]; maybe_get_repository(_, SessionPid, Reserve, SdrInfo) -> [{sdr_info, SdrInfo}] ++ maybe_reserve(SessionPid, fun read_all/2, [SessionPid], Reserve). %%------------------------------------------------------------------------------ %% @doc %% Read one specific entry from the sensor data record repository (SDR). %% @end %%------------------------------------------------------------------------------ -spec get(pid(), non_neg_integer()) -> entry(). get(SessionPid, RecordId) -> {ok, SdrInfo} = eipmi_session:rpc(SessionPid, ?GET_INFO, []), Args = [SessionPid, RecordId], Reserve = lists:member(reserve, proplists:get_value(operations, SdrInfo)), {_, Entry} = maybe_reserve(SessionPid, fun read_one/3, Args, Reserve), Entry. %%------------------------------------------------------------------------------ %% @doc %% Return the current reading of a specific sensor referred to by its number. %% @end %%------------------------------------------------------------------------------ -spec get_sensor_reading(pid(), non_neg_integer(), [entry()]) -> [reading()]. get_sensor_reading(SessionPid, SensorNumber, SdrRepository) -> Pred = fun(Ps) -> lists:member({sensor_number, SensorNumber}, Ps) end, [Sdr] = [Record || Record = {_, Ps} <- SdrRepository, Pred(Ps)], get_sensor_reading(SessionPid, Sdr). %%------------------------------------------------------------------------------ %% @doc %% Return the current reading of a specific sensor referred to by its sensor %% data record. %% @end %%------------------------------------------------------------------------------ -spec get_sensor_reading(pid(), {full | compact, [property()]}) -> [reading()]. get_sensor_reading(SessionPid, {Type, Properties}) when Type =:= full orelse Type =:= compact -> ReadingType = proplists:get_value(reading_type, Properties), SensorType = proplists:get_value(sensor_type, Properties), Args = [lists:keyfind(sensor_number, 1, Properties)], {ok, Result} = eipmi_session:rpc(SessionPid, ?GET_READING, Args), States = proplists:get_value(raw_states, Result), Raw = proplists:get_value(raw_reading, Result), Reading = get_reading(sensor_reading, Raw, Properties), get_states(ReadingType, SensorType, States) ++ Reading. %%------------------------------------------------------------------------------ %% @doc %% Convert a raw sensor reading into a meaningful value, using the sensors %% full SDR record. %% @end %%------------------------------------------------------------------------------ -spec convert(binary(), {full, [property()]}) -> [reading()]. convert(Raw, {full, Properties}) -> get_reading(sensor_reading, Raw, Properties). %%------------------------------------------------------------------------------ %% @doc %% Returns a string representation of the SDR repository or of a single SDR. %% @end %%------------------------------------------------------------------------------ -spec to_list(entry() | [entry()]) -> string(). to_list(SdrRepository) when is_list(SdrRepository) -> eipmi_util:join_nl( ["RECORD TYPE NUM ENTITY ID", "---------------------------------------------------------------------"] ++ [to_list(E) || E = {T, _} <- SdrRepository, T =/= sdr_info]); to_list({T, Properties}) -> RecordId = proplists:get_value(record_id, Properties), RecordType = string:to_upper(atom_to_list(T)), Type = eipmi_sensor:to_list(proplists:get_value(sensor_type, Properties, "-")), Num = eipmi_sensor:to_list(proplists:get_value(sensor_number, Properties, "-")), EntityId = proplists:get_value(entity_id, Properties, "-"), Inst = proplists:get_value(entity_instance, Properties, -1), EntityStr = io_lib:format("~s/~-2.B", [eipmi_sensor:to_list(EntityId), Inst]), Id = proplists:get_value(id, Properties, "-"), eipmi_util:format( "~-6.B ~-14.14s ~-15.15s ~-3.3s ~-15.15s ~s", [RecordId, RecordType, Type, Num, EntityStr, Id]). %%%============================================================================= %%% Internal functions %%%============================================================================= %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -spec maybe_reserve(pid(), function(), [term()], boolean()) -> [entry()] | {non_neg_integer(), entry()}. maybe_reserve(SessionPid, Fun, Args, true) -> {ok, Reserve} = eipmi_session:rpc(SessionPid, ?RESERVE, []), ReservationId = proplists:get_value(reservation_id, Reserve), erlang:apply(Fun, Args ++ [ReservationId]); maybe_reserve(_SessionPid, Fun, Args, false) -> erlang:apply(Fun, Args ++ [16#0000]). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ read_all(SessionPid, ReservationId) -> read_all(SessionPid, ReservationId, 16#0000, []). read_all(_SessionPid, _ReservationId, 16#ffff, Acc) -> lists:reverse(Acc); read_all(SessionPid, ReservationId, RecordId, Acc) -> {NextRecordId, Entry} = read_one(SessionPid, RecordId, ReservationId), read_all(SessionPid, ReservationId, NextRecordId, [Entry | Acc]). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ read_one(SessionPid, RecordId, ReservationId) -> {NextRecordId, Header} = read_header(SessionPid, RecordId, ReservationId), Length = proplists:get_value(record_length, Header), Type = proplists:get_value(record_type, Header), Body = read_body(SessionPid, RecordId, ReservationId, Type, Length), {NextRecordId, {Type, proplists:delete(record_type, Header) ++ Body}}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ read_header(Pid, Record, Reservation) -> Ps = [{reservation_id, Reservation}, {record_id, Record}, {offset, 0}, {count, 5}], {ok, Read} = eipmi_session:rpc(Pid, ?READ, Ps), NextRecordId = proplists:get_value(next_record_id, Read), {NextRecordId, decode_header(proplists:get_value(data, Read))}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ read_body(SessionPid, RecordId, ReservationId, Type, Length) -> decode_body(Type, read_body(SessionPid, RecordId, ReservationId, Length, ?MAX_READ_COUNT, {0, <<>>})). read_body(_Pid, _Record, _Reservation, Len, _Count, {Len, Data}) -> Data; read_body(Pid, Record, Reservation, Len, Count, Acc = {Offset, _}) when Offset + Count =< Len -> NewAcc = do_read(Pid, Record, Reservation, Count, Acc), read_body(Pid, Record, Reservation, Len, Count, NewAcc); read_body(Pid, Record, Reservation, Len, Count, Acc = {Offset, _}) -> NewAcc = do_read(Pid, Record, Reservation, Len - Offset, Acc), read_body(Pid, Record, Reservation, Len, Count, NewAcc). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ do_read(Pid, Record, Reservation, Count, {Offset, Acc}) -> Ps = [{reservation_id, Reservation}, {record_id, Record}, {offset, 5 + Offset}, {count, Count}], {ok, Read} = eipmi_session:rpc(Pid, ?READ, Ps), {Offset + Count, <>}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_header(<>) -> T = get_record_type(Type), [V1 | VRest] = lists:reverse(eipmi_util:from_bcd_plus(Version)), V = [V1 | [$. | VRest]], [{record_id, Id}, {sdr_version, V}, {record_type, T}, {record_length, L}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_body(full, Data) -> decode_full_sensor_record(Data); decode_body(compact, Data) -> decode_compact_sensor_record(Data); decode_body(event_only, Data) -> decode_event_only_record(Data); decode_body(entity_association, Data) -> decode_entity_association_record(Data); decode_body(device_relative_entity_association, Data) -> decode_device_relative_entity_association_record(Data); decode_body(generic_device_locator, Data) -> decode_generic_device_locator_record(Data); decode_body(fru_device_locator, Data) -> decode_fru_device_locator_record(Data); decode_body(management_controller_device_locator, Data) -> decode_management_controller_device_locator_record(Data); decode_body(management_controller_confirmation, Data) -> decode_management_controller_confirmation_record(Data); decode_body(bmc_message_channel_info, Data) -> decode_bmc_message_channel_info_record(Data); decode_body(oem, Data) -> decode_oem_record(Data). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_record_type(16#01) -> full; get_record_type(16#02) -> compact; get_record_type(16#03) -> event_only; get_record_type(16#08) -> entity_association; get_record_type(16#09) -> device_relative_entity_association; get_record_type(16#10) -> generic_device_locator; get_record_type(16#11) -> fru_device_locator; get_record_type(16#12) -> management_controller_device_locator; get_record_type(16#13) -> management_controller_confirmation; get_record_type(16#14) -> bmc_message_channel_info; get_record_type(16#c0) -> oem; get_record_type(_) -> reserved. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_full_sensor_record( <>) -> {Reading, Type} = eipmi_sensor:get_type(ReadingType, SensorType), Units = get_units(SensorUnit), Properties = get_sensor_properties(SensorProperties, Units), eipmi_sensor:get_addr(SensorAddr) ++ get_sensor_numbers(SensorNumber, 0) ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ [{sensor_type, Type}, {reading_type, Reading}] ++ Units ++ Properties ++ get_reading(nominal_reading, NominalReading, Units ++ Properties) ++ get_reading(nominal_maximum, NominalMaximum, Units ++ Properties) ++ get_reading(nominal_minimum, NominalMinimum, Units ++ Properties) ++ get_reading(maximum_reading, MaximumReading, Units ++ Properties) ++ get_reading(minimum_reading, MinimumReading, Units ++ Properties) ++ get_sensor_id(Id). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_compact_sensor_record( <>) -> {Reading, Type} = eipmi_sensor:get_type(ReadingType, SensorType), eipmi_sensor:get_addr(SensorAddr) ++ get_sensor_numbers(SensorNumber, Count) ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ [{sensor_type, Type}, {reading_type, Reading}] ++ get_units(SensorUnit) ++ get_direction(Direction) ++ get_id(Count, Sharing, Modifier, get_id(Id)). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_event_only_record( <>) -> {Reading, Type} = eipmi_sensor:get_type(ReadingType, SensorType), eipmi_sensor:get_addr(SensorAddr) ++ get_sensor_numbers(SensorNumber, Count) ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ [{sensor_type, Type}, {reading_type, Reading}] ++ get_direction(Direction) ++ get_id(Count, Sharing, Modifier, get_id(Id)). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_entity_association_record( <>) -> [{container, eipmi_sensor:get_entity(Id, Instance)}, {linked_records_exist, eipmi_util:get_bool(RecordLink)}, {presence_sensor_always_accessible, eipmi_util:get_bool(Presence)}] ++ get_contained(Range, direct, Data). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_device_relative_entity_association_record( <>) -> [{container, eipmi_sensor:get_entity(Id, Inst) ++ get_relative_addr(Addr)}, {linked_records_exist, eipmi_util:get_bool(RecordLink)}, {presence_sensor_always_accessible, eipmi_util:get_bool(Presence)}] ++ get_contained(Range, relative, Data). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_generic_device_locator_record( <>) -> [{access_addr, AccessAddr}] ++ get_generic_addr(Addr) ++ [{device_type, Type}, {device_type_modifier, TypeModifier}] ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ get_sensor_id(Id). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_fru_device_locator_record( <>) -> [{access_addr, AccessAddr}] ++ eipmi_sensor:get_addr(DeviceAddr) ++ [{device_type, Type}, {device_type_modifier, TypeModifier}] ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ get_sensor_id(Id). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_management_controller_device_locator_record( <>) -> eipmi_sensor:get_addr(<>) ++ [{device_support, eipmi_response:get_device_support(Capabilities)}] ++ eipmi_sensor:get_entity(EntityId, EntityInstance) ++ get_sensor_id(Id). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_management_controller_confirmation_record( <>) -> [Iv1 | IvRest] = lists:reverse(eipmi_util:from_bcd_plus(IPMIVersion)), [{firmware_version, eipmi_util:format("~B.~B", [Major, Minor])}, {ipmi_version, [Iv1 | [$. | IvRest]]}, {manufacturer_id, Manufacturer}, {product_id, Product}, {guid, eipmi_util:binary_to_string(GUID)}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_bmc_message_channel_info_record( <>) -> [{msg_channel_info, get_message_channel_info(C0)}, {msg_channel_info, get_message_channel_info(C1)}, {msg_channel_info, get_message_channel_info(C2)}, {msg_channel_info, get_message_channel_info(C3)}, {msg_channel_info, get_message_channel_info(C4)}, {msg_channel_info, get_message_channel_info(C5)}, {msg_channel_info, get_message_channel_info(C6)}, {msg_channel_info, get_message_channel_info(C7)}] ++ get_interrupt_type(messaging_interrupt, MsgInterrupt) ++ get_interrupt_type(event_message_buffer_interrupt, EventInterrupt). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_oem_record(<>) -> [{manufacturer_id, Manufacturer}, {data, Data}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_units(<>) -> get_format(Format) ++ get_rate(Rate) ++ get_unit(Modifier, Base, Mod) ++ [{percentage, eipmi_util:get_bool(Percentage)}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_format(0) -> [{sensor_format, unsigned}]; get_format(1) -> [{sensor_format, ones_complement}]; get_format(2) -> [{sensor_format, twos_complement}]; get_format(3) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_rate(1) -> [{sensor_rate, eipmi_sensor:get_unit(20)}]; get_rate(2) -> [{sensor_rate, eipmi_sensor:get_unit(21)}]; get_rate(3) -> [{sensor_rate, eipmi_sensor:get_unit(22)}]; get_rate(4) -> [{sensor_rate, eipmi_sensor:get_unit(23)}]; get_rate(5) -> [{sensor_rate, eipmi_sensor:get_unit(24)}]; get_rate(6) -> [{sensor_rate, eipmi_sensor:get_unit(25)}]; get_rate(_) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_unit(0, Base, _) -> [{sensor_unit, eipmi_sensor:get_unit(Base)}]; get_unit(_, Base, 16#00) -> [{sensor_unit, eipmi_sensor:get_unit(Base)}]; get_unit(1, Base, Mod) -> M = eipmi_sensor:get_unit(Mod), [{sensor_unit, {eipmi_sensor:get_unit(Base), per, M}}]; get_unit(2, Base, Mod) -> M = eipmi_sensor:get_unit(Mod), [{sensor_unit, {eipmi_sensor:get_unit(Base), M}}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_linearization(16#00) -> linear; get_linearization(16#01) -> ln; get_linearization(16#02) -> log10; get_linearization(16#03) -> log2; get_linearization(16#04) -> e; get_linearization(16#05) -> exp10; get_linearization(16#06) -> exp2; get_linearization(16#07) -> '1/x'; get_linearization(16#08) -> sqr; get_linearization(16#09) -> cube; get_linearization(16#0a) -> sqrt; get_linearization(16#0b) -> 'cube-1'; get_linearization(16#70) -> non_linear; get_linearization(L) when L =< 16#7f andalso L >= 16#71 -> oem_non_linear. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_linearization_fun(ln) -> fun(X) -> math:log(X) end; get_linearization_fun(log10) -> fun(X) -> math:log10(X) end; get_linearization_fun(log2) -> fun(X) -> math:log(X) / math:log(2) end; get_linearization_fun(e) -> fun(X) -> math:exp(X) end; get_linearization_fun(exp10) -> fun(X) -> math:pow(10, X) end; get_linearization_fun(exp2) -> fun(X) -> math:pow(2, X) end; get_linearization_fun('1/x') -> fun(X) when X /= 0 -> 1 / X; (X) -> X end; get_linearization_fun(sqr) -> fun(X) -> X * X end; get_linearization_fun(cube) -> fun(X) -> X * X * X end; get_linearization_fun(sqrt) -> fun(X) -> math:sqrt(X) end; get_linearization_fun('cube-1') -> fun(X) -> math:pow(X, 1 / 3) end; get_linearization_fun(_) -> fun(X) -> X end. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_sensor_properties(Binary, Properties) -> Unit = proplists:get_value(sensor_unit, Properties), get_sensor_properties(Unit =/= unspecified, Binary, Unit). get_sensor_properties( true, <>, Unit) -> get_sensor_properties( <>, <>, <>, Linearization, Tolerance, AccuracyExp, Direction, ResultExp, BExp, Unit); get_sensor_properties(false, _, _) -> []. get_sensor_properties( <>, <>, <>, Linearization, Tolerance, AccuracyExp, Direction, ResultExp, BExp, Unit) -> L = get_linearization(Linearization), LFun = get_linearization_fun(L), [{sensor_tolerance, {LFun(M * Tolerance / 2 * math:pow(10, ResultExp)), Unit}}, {sensor_resolution, {abs(M * math:pow(10, ResultExp)), Unit}}, {sensor_accuracy, {math:pow(Accuracy, AccuracyExp) / 100, percent}}, {sensor_coefficients, {L, M, B, BExp, ResultExp}}] ++ get_direction(Direction). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_sensor_numbers(SensorNumber, 0) -> [{sensor_number, SensorNumber}]; get_sensor_numbers(SensorNumber, 1) -> [{sensor_number, SensorNumber}]; get_sensor_numbers(SensorNumber, ShareCount) -> [{sensor_numbers, [SensorNumber + I || I <- lists:seq(0, ShareCount - 1)]}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_direction(0) -> []; get_direction(1) -> [{sensor_direction, input}]; get_direction(2) -> [{sensor_direction, output}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_sensor_id(Binary) -> [{id, get_id(Binary)}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_id(<<_:2, ?EIPMI_RESERVED:1, 0:5, _/binary>>) -> ""; get_id(<<0:2, ?EIPMI_RESERVED:1, _Len:5, Id/utf16-little, _/binary>>) -> Id; get_id(<<1:2, ?EIPMI_RESERVED:1, Len:5, Id:Len/binary, _/binary>>) -> eipmi_util:from_bcd_plus(Id); get_id(<<2:2, ?EIPMI_RESERVED:1, Len:5, Id:Len/binary, _/binary>>) -> eipmi_util:from_packed_ascii(Id); get_id(<<_:2, ?EIPMI_RESERVED:1, Len:5, Id:Len/binary, _/binary>>) -> eipmi_util:binary_to_string(Id); get_id(<<_:8, Id/binary>>) -> eipmi_util:binary_to_string(Id). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_id(0, Sharing, Type, Id) -> get_id(1, Sharing, Type, Id); get_id(_, <<0:1, _:7>>, _, Id) -> [{id, Id}]; get_id(Count, <<1:1, Offset:7>>, 0, Id) -> Is = lists:seq(Offset, Offset + Count - 1), [{id, string:join([Id ++ integer_to_list(I) || I <- Is], ", ")}]; get_id(Count, <<1:1, Offset:7>>, 1, Id) -> Is = lists:seq(Offset, Offset + Count - 1), [{id, string:join([Id ++ eipmi_util:from_base26(I) || I <- Is], ", ")}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_lun(<<0:1, Lun:2>>) -> [{sensor_lun, Lun}]; get_lun(_) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_generic_addr(<>) -> get_generic_addr(Addr, Span) ++ [{sensor_lun, Lun}]; get_generic_addr(<>) -> get_generic_addr(Addr, Span) ++ [{sensor_lun, Lun}, {bus_id, Bus}]; get_generic_addr(<>) -> get_generic_addr(Addr, Span) ++ [{channel, Chan}, {sensor_lun, Lun}]; get_generic_addr(<>) -> get_generic_addr(Addr, Span) ++ [{channel, Chan}, {sensor_lun, Lun}, {bus_id, Bus}]. get_generic_addr(Addr, Span) -> [{sensor_addr, Addr + I} || I <- lists:seq(0, Span)]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_relative_addr(<<0:7, ?EIPMI_RESERVED:1, 0:4, ?EIPMI_RESERVED:4>>) -> []; get_relative_addr(<>) -> [{sensor_addr, Addr}]; get_relative_addr(<>) -> [{sensor_addr, Addr}, {channel, Chan}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_protocol(16#1) -> [{protocol, ipmb}]; get_protocol(16#2) -> [{protocol, {icmb, 1.0}}]; get_protocol(16#3) -> [{protocol, {icmb, 0.9}}]; get_protocol(16#4) -> [{protocol, {sm_bus, 1.0}}]; get_protocol(16#5) -> [{protocol, system_format}]; get_protocol(16#c) -> [{protocol, {oem, 1}}]; get_protocol(16#d) -> [{protocol, {oem, 2}}]; get_protocol(16#e) -> [{protocol, {oem, 3}}]; get_protocol(16#f) -> [{protocol, {oem, 4}}]; get_protocol(_) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_message_channel_info(<>) -> [{transmit_support, eipmi_util:get_bool(T)}] ++ get_lun(Lun) ++ get_protocol(Protocol). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_interrupt_type(Tag, IRQ) when IRQ < 16#10 -> [{Tag, {irq, IRQ}}]; get_interrupt_type(Tag, PCI) when PCI < 16#14 -> [{Tag, {pci, [49 + PCI]}}]; get_interrupt_type(Tag, 16#14) -> [{Tag, smi}]; get_interrupt_type(Tag, 16#15) -> [{Tag, sci}]; get_interrupt_type(Tag, I) when I < 16#5f-> [{Tag, {interrupt, I - 16#20}}]; get_interrupt_type(Tag, 16#60) -> [{Tag, assigned}]; get_interrupt_type(_Tag, _) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_contained(0, direct, Binary) -> get_contained_from_list(Binary, []); get_contained(0, relative, Binary) -> get_rel_contained_from_list(Binary, []); get_contained(1, direct, Binary) -> get_contained_from_range(Binary, []); get_contained(1, relative, Binary) -> get_rel_contained_from_range(Binary, []). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_contained_from_list(<<>>, Acc) -> lists:reverse(Acc); get_contained_from_list(<<0:16, Rest/binary>>, Acc) -> get_contained_from_list(Rest, Acc); get_contained_from_list(<>, Acc) -> Entity = {containee, eipmi_sensor:get_entity(Id, I)}, get_contained_from_list(R, [Entity | Acc]). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_contained_from_range(<<>>, Acc) -> Acc; get_contained_from_range(<<0:32, Rest/binary>>, Acc) -> get_contained_from_range(Rest, Acc); get_contained_from_range(<>, Acc) -> Es = [{containee, eipmi_sensor:get_entity(Id, I)} || I <- lists:seq(I0, I1)], get_contained_from_range(Rest, Acc ++ Es). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_rel_contained_from_list(<<>>, Acc) -> lists:reverse(Acc); get_rel_contained_from_list(<<0:32, Rest/binary>>, Acc) -> get_rel_contained_from_list(Rest, Acc); get_rel_contained_from_list(<>, Acc) -> Entity = {containee, eipmi_sensor:get_entity(Id, I) ++ get_relative_addr(A)}, get_rel_contained_from_list(R, [Entity | Acc]). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_rel_contained_from_range(<<>>, Acc) -> Acc; get_rel_contained_from_range(<<0:64, Rest/binary>>, Acc) -> get_rel_contained_from_range(Rest, Acc); get_rel_contained_from_range( <>, Acc) -> Es = [{containee, eipmi_sensor:get_entity(Id, I) ++ get_relative_addr(A)} || I <- lists:seq(I0, I1)], get_rel_contained_from_range(R, Acc ++ Es). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_reading(Tag, Raw, Properties) -> Unit = proplists:get_value(sensor_unit, Properties), Format = proplists:get_value(sensor_format, Properties), Coefficients = proplists:get_value(sensor_coefficients, Properties), get_reading(Tag, Unit, Format, Raw, Coefficients). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_reading(_, U, _, _, _) when U =:= unspecified orelse U =:= undefined -> []; get_reading(Tag, Unit, unsigned, <>, Coefficients) -> [{Tag, calc_reading(Unit, Value, Coefficients)}]; get_reading(Tag, Unit, ones_complement, <<1:1, Raw:7>>, Coefficients) -> [{Tag, calc_reading(Unit, (bnot Raw) * -1, Coefficients)}]; get_reading(Tag, Unit, ones_complement, <<0:1, Value:7>>, Coefficients) -> [{Tag, calc_reading(Unit, Value, Coefficients)}]; get_reading(Tag, Unit, twos_complement, <>, Coefficients) -> [{Tag, calc_reading(Unit, Value, Coefficients)}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ calc_reading(Unit, X, {L, M, B, BExp, ResultExp}) -> Fun = get_linearization_fun(L), {Fun((M * X + B * math:pow(10, BExp)) * math:pow(10, ResultExp)), Unit}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_states(threshold, _, <<_:2, Threshold:6, _/binary>>) -> [{sensor_threshold, get_threshold(Threshold)}]; get_states(R, T, <>) -> lists:append( [eipmi_sensor:get_value(R, T, O, 1, 16#ff, 16#ff) || O <- get_offsets(First, 7, [])]); get_states(R, T, <>) -> lists:append( [eipmi_sensor:get_value(R, T, O, 1, 16#ff, 16#ff) || O <- get_offsets(<>, 14, [])]); get_states(R, T, <>) -> lists:append( [eipmi_sensor:get_value(R, T, O, 1, 16#ff, 16#ff) || O <- get_offsets(First, 7, [])]); get_states(_, _, _) -> []. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ needs_reservation(SdrInfo) -> lists:member(reserve, proplists:get_value(operations, SdrInfo)). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_threshold(N) when N >= 32 -> upper_non_recoverable; get_threshold(N) when N >= 16 -> upper_critical; get_threshold(N) when N >= 8 -> upper_non_critical; get_threshold(N) when N >= 4 -> lower_non_recoverable; get_threshold(N) when N >= 2 -> lower_critical; get_threshold(_) -> lower_non_critical. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_offsets(<<>>, _, Acc) -> lists:reverse(Acc); get_offsets(<<0:1, R/bitstring>>, I, Acc) -> get_offsets(R, I - 1, Acc); get_offsets(<<1:1, R/bitstring>>, I, Acc) -> get_offsets(R, I - 1, [I | Acc]). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ get_recent_action_timestamp(SdrInfo) -> erlang:max( to_timestamp(proplists:get_value(most_recent_addition, SdrInfo, 0)), to_timestamp(proplists:get_value(most_recent_erase, SdrInfo, 0))). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ to_timestamp(16#ffffffff) -> -1; to_timestamp(Val) -> Val.