IBS-Sensor-Library/src/IBS_Sensor.cpp

344 lines
13 KiB
C++

// IBS_Sensor.cpp
// Interfaces a Hella IBS-200X Battery Sensor via Lin-BUS
// Provides voltage, curret, State of Charge, etc.
//
// Copyright mestrode <ardlib@mestro.de>
// Original Source: https://github.com/mestrode/IBS-Sensor-Library
//
// Requires class Lin_Interface: https://github.com/mestrode/Lin-Interface-Library
//
// Includes informations provided by breezer
// https://www.kastenwagenforum.de/forum/threads/diy-hella-ibs-batteriecomputer.31724/page-2
// Includes informations from the code created by Frank Schöniger
// https://github.com/frankschoeniger/LIN_Interface
#include "IBS_Sensor.hpp"
//-----------------------------------------------------------------------------
// LIN Specification said abaout FrameIDs:
// 0-50 (0x00-0x3B) are used for normal Signal/data carrying frames.
// 60 (0x3C) and 61 (0x3D) are used to carry diagnostic and configuration data.
// 62 (0x3E) and 63 (0x3F) are reserved for future protocol enhancements.
//-----------------------------------------------------------------------------
// Frame IDs depends on SensorNo ("Sensor 1" or "Sensor 2" is marked on the label)
#define IBS_FRM_STA 0 /* 0x27 */
#define IBS_FRM_CUR 1 /* 0x28 */
#define IBS_FRM_ERR 2 /* 0x29 */
#define IBS_FRM_tb3 3 /* 0x2A */
#define IBS_FRM_SOx 4 /* 0x2B */
#define IBS_FRM_CAP 5 /* 0x2C */
// 0 1 2 3 4 5
// STA CUR ERR tb3 SOx CAP
const uint8_t IBS_FrameID[2][6] = {{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, // "Sensor 1"
{0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C}}; // "Sensor 2"
//-----------------------------------------------------------------------------
// There are additional Frames, the Sensor is responding to
// If you send one of those Frames to "Sensor 2", it will response:
// ID 34h - Resonse: 20:4E:20:4E:00:00:00:00
// ID 35h - Resonse: 00:00:00:00:00:00:00:00
// ID 36h - Resonse: 00:00:08:00:F8:FF
// ID 37h - Resonse: F4:2E:2D:00:00:00:00:00
// What does it mean?
//-----------------------------------------------------------------------------
// constructor
// constructor of class IBS_Sensor
IBS_Sensor::IBS_Sensor(int SensorNo) {
// "Sensor 1" = 1 => _SensorNo = 0;
// "Sensor 2" = 2 => _SensorNo = 1;
_SensorNo = SensorNo-1;
};
//-----------------------------------------------------------------------------
// Request current valus form sensor
/// Read current sensor data (at least all usefull frames)
/// @returns success of _all_ operations
bool IBS_Sensor::readFrames()
{
//ensure not work with received data (in case of a chksum error at the first run)
StatusReady = false;
//Read max 3 times sensor status / wait for valid data
for (int i=3; i>=0; i--) {
// StatusReady Flag is included in Frame "Status"
readFrameStatus();
if (StatusReady)
break;
}
if (!StatusReady) {
return false;
}
bool success = readFrameCurrent();
success = success && readFrameSOx();
success = success && readFrameCapacity();
// Dont know that to do with the results of Frame Error and TB3
// success = success && readFrameError();
// success = success && readFrameTB3();
return success;
}
// Request Frame "Status": only Ready Flag is asumed
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameStatus()
{
// FrameID "Sensor 1" = 21h
// "Sensor 2" = 27h
// ID 27h - STA - D1 = identification, Bereitschaft des Sensors?
// LL = Statusbyte
// 01 = Sensor ready, data available flag ?
// Sends usually data like 192 194 208=linked to Cap_Max?
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_STA]);
if (chkSumValid)
{
StatusByte = LinBus->LinMessage[0];
// Bit 0 seemed to be a kind of Data Ready Flag
StatusReady = LinBus->LinMessage[0] & 0x01;
}
return chkSumValid;
}
/// Request Frame CUR: Ubat, Ibat, Tbat and unknown1
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameCurrent()
{
// FrameID "Sensor 1" = 22h
// "Sensor 2" = 28h
// ID 28h - CUR - AB:84:1E:F4:2E:84:7A
// IL IM IH = Ibat (x-2000000)/1000 Ampere, positive Werte = Batterie laden
// UL UH = Ubat x/1000 Volt
// TT = Tbat x/2-40 °C
// ?? = 0x7A and 0c7C observed, changes with event on unknown6?
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_CUR]);
if (chkSumValid)
{
Ibat = (float((long(LinBus->LinMessage[2]) << 16) + (long(LinBus->LinMessage[1]) << 8) + long(LinBus->LinMessage[0]) - 2000000L)) / 1000.0;
Ubat = (float((LinBus->LinMessage[4] << 8) + LinBus->LinMessage[3])) / 1000.0;
Tbat = float(LinBus->LinMessage[5]) / 2 - 40;
unknown1 = LinBus->LinMessage[6];
}
return chkSumValid;
}
/// Request Frame "Error": only one Byte maybe with some Error Flags
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameError()
{
// FrameID "Sensor 1" = 23h
// "Sensor 2" = 29h
// ID 29h - ERR - 00
// LL = Error flags or code
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_ERR]);
if (chkSumValid)
{
ErrorByte = LinBus->LinMessage[0]; // IBS_Error bit code?
}
return chkSumValid;
}
/// Request Frame tb3: 4 bytes, but unknown content
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameTB3()
{
// FrameID "Sensor 1" = 24h
// "Sensor 2" = 2Ah
// ID 2Ah - tb3 - 00:00:00:00
// HH LL = unknown2
// HH LL = unknown3
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_tb3]);
if (chkSumValid)
{
unknown2 = (LinBus->LinMessage[1] << 8) + LinBus->LinMessage[0];
unknown3 = (LinBus->LinMessage[3] << 8) + LinBus->LinMessage[2];
}
return chkSumValid;
}
/// Request Frame SOH: SOH, SOC and 2 unknown Bytes and a unknown Word
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameSOx()
{
// FrameID "Sensor 1" = 25h
// "Sensor 2" = 2Bh
// ID 2Bh - SOx - 2D:C8:FF:BB:00:00
// HH = State Of Charge x/2 in Prozent
// CC = State Of Health x/2 in Prozent
// ?? = unknown4 / has correlation to Cap_Available or SOC?
// ?? = unknown5 / no direkt link to unkown 4?
// H?:L? = unknown6 / maybe some corelation to Cap_Available or SOC?
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_SOx]);
if (chkSumValid)
{
SOC = float(LinBus->LinMessage[0]) / 2; // state of health
SOH = float(LinBus->LinMessage[1]) / 2; // state of charge
unknown4 = LinBus->LinMessage[2]; // seemed to be a byte value
unknown5 = LinBus->LinMessage[3]; // seemed to be a byte value
unknown6 = (LinBus->LinMessage[5] << 8) + LinBus->LinMessage[4]; // word or 2 bytes, not verified
}
return chkSumValid;
}
/// Request Frame "Capacity": max seen, available and configured Capacity, Calibration
/// @returns checksum of frame is valid, data accepted
bool IBS_Sensor::readFrameCapacity()
{
// FrameID "Sensor 1" = 26h
// "Sensor 2" = 2Ch
// ID 2Ch - CAP - 20:03:B4:00:50:FE
// HH LL = Max seen Capacity x/10 Ah (=SOH ?)
// AL AH = Available Capacity x/10 Ah (=SOC)
// AH = Configured Capacity
// FF = CalibByte, maybe filled with stuffing bits?
// 01 = CalibrationDone flag, 1=ok, 0=uncalibrated
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_CAP]);
if (chkSumValid)
{
Cap_Max = (float((LinBus->LinMessage[1] << 8) + LinBus->LinMessage[0])) / 10; // max. seen available Cap
Cap_Available = (float((LinBus->LinMessage[3] << 8) + LinBus->LinMessage[2])) / 10; // Available Capacity
Cap_Configured = LinBus->LinMessage[4]; // configured Cap
CalibByte = LinBus->LinMessage[5]; // eigentlich ist nur das Bit0 wichtig?
CalibrationDone = bitRead(LinBus->LinMessage[5], 0); // 1=calibration done 0=not finished yet
}
return chkSumValid;
}
//-----------------------------------------------------------------------------
// configuration of Sensor - only needed once: data will be stored in sensor
/// Configure BatSensor by sending 3 Configuration-Frames
void IBS_Sensor::writeConfiguration(IBS_BatteryTypes BatType, uint8_t BatCapacity)
{
writeUnknownParam(); // Not sure why
writeBatCapacity(BatCapacity); // nominal capacity (Ah)
writeBatType(BatType); // battery type (AGM, GEL or STARTER)
}
/// Write configuration Parameter "Unknown"
/// Parameter will be written by the configuration procedure used by the main panel
/// so I don't want to break tradition...
/// @returns no verification of success!!!
void IBS_Sensor::writeUnknownParam()
{
// Function of ths configuration Frame is unknown!
// Frame is send in configuration procedure by control panel, so why miss this out?
// guess this frame does
// - general Reset of the sensor?
// - configure initial battery status? (0x7F = 50% charge state)
// Request for configuration by main panel
// 00005.731 3c 02 06 b2 3a ff 7f ff ff 8b ERR
// ^^
// Response of sensor
// 00005.780 7d 02 02 f2 0a ff ff ff ff fe ERR
// ^^ guess this was not successful
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
LinBus->LinMessage[0] = 0x02; // Sensor No
LinBus->LinMessage[1] = 0x06; // Data Len
LinBus->LinMessage[2] = 0xB2; // CMD Config Read
LinBus->LinMessage[3] = 0x3A; // Config Type
LinBus->LinMessage[4] = 0xFF; // the unknown message = reset configuration?
LinBus->LinMessage[5] = 0x7F; // obviously not the first but the second byte will be written
LinBus->LinMessage[6] = 0xFF;
LinBus->LinMessage[7] = 0xFF;
LinBus->writeFrame(0x3C, 8);
LinBus->readFrame(0x3D);
}
/// Write configuration Parameter "Capacity" = Value of Ah of the Battery, default factory value = "80"Ah
/// @param BatCapacity labeled capacity (in Ah) of the lead battery
/// @returns no verification of success!!!
void IBS_Sensor::writeBatCapacity(uint8_t BatCapacity)
{
// Configuration (Of Sensor Type 1)
// Battery capacity can be read back on 0x2C Byte 4
// ID 3Ch - Capacity 02:03:B5:39: BatCap :FF:FF:FF - BatCap in Ah
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
LinBus->LinMessage[1] = 0x03; // Data Len
LinBus->LinMessage[2] = 0xB5; // CMD Config Read
LinBus->LinMessage[3] = 0x39; // Config Type
LinBus->LinMessage[4] = BatCapacity; // e.g. 70 Ah
LinBus->LinMessage[5] = 0xFF; // filling bytes
LinBus->LinMessage[6] = 0xFF;
LinBus->LinMessage[7] = 0xFF;
LinBus->writeFrame(0x3C, 8);
LinBus->readFrame(0x3D);
}
/// Write configuration Parameter "Battery Type" and read answer back (not judged)
/// @param BatType
/// @returns no verification of success!!!
void IBS_Sensor::writeBatType(IBS_BatteryTypes BatType)
{
// Battery Type can not be verified by using another Frame, since no Frame includes this data
// But recalibration is obviously needed, so CalibrationDone Flag should be an indicator for success
/* Procedure
00012.919 ec bc 02 dc 01 46 FF 30 CAP Sensor 2, is calibrated (FF)
00012.935 3c 02 03 b5 3a 1e ff ff ff ec ERR Config Sensor 2 Len 3 Conf =B5 Type 3a Data 1e
^^ = AGM parameter to be written
00012.988 7d 02 03 f5 3a 1e ff ff ff ac ERR Answer Sensor 2 Len 3 Conf+40=F5 Type 3a Data 1e
^^ = AGM readback --> confirmation of parameter from device
^^ = len+40 --> valid
00013.115 ec bc 02 dc 01 46 FE 31 CAP Sensor 2, is not calibrated (FE)
^ need to calibrate
*/
// ID 3Ch - STARTER Bat 02:03:B5:3A: 0A :FF:FF:FF|01
// GEL Bat 02:03:B5:3A: 14 :FF:FF:FF|F6
// AGM Bat 02:03:B5:3A: 1E :FF:FF:FF|EC
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
LinBus->LinMessage[1] = 0x03; // Data Len
LinBus->LinMessage[2] = 0xB5; // CMD Config Read
LinBus->LinMessage[3] = 0x3A; // Config Type
LinBus->LinMessage[5] = 0xFF; // filling bytes
LinBus->LinMessage[6] = 0xFF;
LinBus->LinMessage[7] = 0xFF;
switch (BatType)
{
case BAT_TYPE_STARTER:
LinBus->LinMessage[4] = 0x0a; // Starter Battery
break;
case BAT_TYPE_GEL:
LinBus->LinMessage[4] = 0x14; // GEL Battery
break;
case BAT_TYPE_AGM:
LinBus->LinMessage[4] = 0x1e; // AGM Battery
break;
}
LinBus->writeFrame(0x3C, 8);
LinBus->readFrame(0x3D);
}