853 lines
22 KiB
C
853 lines
22 KiB
C
/* pijuice.c Driver for the PiJuice HAT (www.pijuice.com), addressed via i2c.
|
|
|
|
Copyright (C) 2019 Andrew Anderson <aander07@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "main.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <stdint.h>
|
|
|
|
/*
|
|
* Linux I2C userland is a bit of a mess until distros refresh to
|
|
* the i2c-tools 4.x release that profides i2c/smbus.h for userspace
|
|
* instead of (re)using linux/i2c-dev.h, which conflicts with a
|
|
* kernel header of the same name.
|
|
*
|
|
* See:
|
|
* https://i2c.wiki.kernel.org/index.php/Plans_for_I2C_Tools_4
|
|
*/
|
|
#if HAVE_LINUX_SMBUS_H
|
|
# include <i2c/smbus.h>
|
|
#endif
|
|
#if HAVE_LINUX_I2C_DEV_H
|
|
# include <linux/i2c-dev.h> /* for I2C_SLAVE */
|
|
# if !HAVE_LINUX_SMBUS_H
|
|
# ifndef I2C_FUNC_I2C
|
|
# include <linux/i2c.h>
|
|
# endif
|
|
# endif
|
|
#endif
|
|
|
|
/*
|
|
* i2c-tools pre-4.0 has a userspace header with a name that conflicts
|
|
* with a kernel header, so it may be ignored/removed by distributions
|
|
* when packaging i2c-tools.
|
|
*
|
|
* This will cause the driver to be un-buildable on certain
|
|
* configurations, so include the necessary bits here to handle this
|
|
* situation.
|
|
*/
|
|
#if WITH_LINUX_I2C
|
|
#if !HAVE_DECL_I2C_SMBUS_ACCESS
|
|
static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command,
|
|
int size, union i2c_smbus_data *data)
|
|
{
|
|
struct i2c_smbus_ioctl_data args;
|
|
__s32 err;
|
|
|
|
args.read_write = read_write;
|
|
args.command = command;
|
|
args.size = size;
|
|
args.data = data;
|
|
|
|
err = ioctl(file, I2C_SMBUS, &args);
|
|
if (err == -1)
|
|
err = -errno;
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
#if !HAVE_DECL_I2C_SMBUS_READ_BYTE_DATA
|
|
static inline __s32 i2c_smbus_read_byte_data(int file, __u8 command)
|
|
{
|
|
union i2c_smbus_data data;
|
|
int err;
|
|
|
|
if ((err = i2c_smbus_access(file, I2C_SMBUS_READ, command,
|
|
I2C_SMBUS_BYTE_DATA, &data)) < 0)
|
|
return err;
|
|
else
|
|
return 0x0FF & data.byte;
|
|
}
|
|
#endif
|
|
|
|
#if !HAVE_DECL_I2C_SMBUS_WRITE_BYTE_DATA
|
|
static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value)
|
|
{
|
|
union i2c_smbus_data data;
|
|
int err;
|
|
|
|
data.byte = value;
|
|
if ((err = i2c_smbus_access(file, I2C_SMBUS_WRITE, command,
|
|
I2C_SMBUS_BYTE_DATA, &data)) < 0)
|
|
return err;
|
|
else
|
|
return 0x0FF & data.byte;
|
|
}
|
|
#endif
|
|
|
|
#if !HAVE_DECL_I2C_SMBUS_READ_WORD_DATA
|
|
static inline __s32 i2c_smbus_read_word_data(int file, __u8 command)
|
|
{
|
|
union i2c_smbus_data data;
|
|
int err;
|
|
|
|
if ((err = i2c_smbus_access(file, I2C_SMBUS_READ, command,
|
|
I2C_SMBUS_WORD_DATA, &data)) < 0)
|
|
return err;
|
|
else
|
|
return 0x0FFFF & data.word;
|
|
}
|
|
#endif
|
|
|
|
#if !HAVE_DECL_I2C_SMBUS_WRITE_WORD_DATA
|
|
static inline __s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value)
|
|
{
|
|
union i2c_smbus_data data;
|
|
int err;
|
|
|
|
data.word = value;
|
|
if ((err = i2c_smbus_access(file, I2C_SMBUS_WRITE, command,
|
|
I2C_SMBUS_WORD_DATA, &data)) < 0)
|
|
return err;
|
|
else
|
|
return 0x0FFFF & data.word;
|
|
}
|
|
#endif
|
|
|
|
#if !HAVE_DECL_I2C_SMBUS_READ_BLOCK_DATA
|
|
static inline __u8* i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length, __u8 *values)
|
|
{
|
|
union i2c_smbus_data data;
|
|
int err;
|
|
|
|
if ( length > I2C_SMBUS_BLOCK_MAX)
|
|
{
|
|
length = I2C_SMBUS_BLOCK_MAX;
|
|
}
|
|
|
|
data.block[0] = length;
|
|
memcpy(data.block + 1, values, length);
|
|
|
|
if ((err = i2c_smbus_access(file, I2C_SMBUS_READ, command,
|
|
I2C_SMBUS_I2C_BLOCK_DATA, &data)) < 0)
|
|
return NULL;
|
|
else
|
|
memcpy(values, &data.block[1], data.block[0]);
|
|
|
|
return values;
|
|
}
|
|
#endif
|
|
#endif // if WITH_LINUX_I2C
|
|
|
|
#define STATUS_CMD 0x40
|
|
#define CHARGE_LEVEL_CMD 0x41
|
|
#define CHARGE_LEVEL_HI_RES_CMD 0x42
|
|
#define FAULT_EVENT_CMD 0x44
|
|
#define BUTTON_EVENT_CMD 0x45
|
|
#define BATTERY_TEMPERATURE_CMD 0x47
|
|
#define BATTERY_VOLTAGE_CMD 0x49
|
|
#define BATTERY_CURRENT_CMD 0x4b
|
|
#define IO_VOLTAGE_CMD 0x4d
|
|
#define IO_CURRENT_CMD 0x4f
|
|
|
|
#define CHARGING_CONFIG_CMD 0x51
|
|
#define BATTERY_PROFILE_ID_CMD 0x52
|
|
#define BATTERY_PROFILE_CMD 0x53
|
|
#define BATTERY_EXT_PROFILE_CMD 0x54
|
|
#define BATTERY_TEMP_SENSE_CONFIG_CMD 0x5D
|
|
|
|
#define POWER_INPUTS_CONFIG_CMD 0x5E
|
|
#define RUN_PIN_CONFIG_CMD 0x5F
|
|
#define POWER_REGULATOR_CONFIG_CMD 0x60
|
|
#define WATCHDOG_ACTIVATION_CMD 0x61
|
|
#define POWER_OFF_CMD 0x62
|
|
#define WAKEUP_ON_CHARGE_CMD 0x63
|
|
#define SYSTEM_POWER_SWITCH_CTRL_CMD 0x64
|
|
|
|
#define LED_STATE_CMD 0x66
|
|
#define LED_BLINK_CMD 0x68
|
|
#define LED_CONFIGURATION_CMD 0x6A
|
|
#define BUTTON_CONFIGURATION_CMD 0x6E
|
|
|
|
#define IO1_CONFIGURATION_CMD 0x72
|
|
#define IO1_PIN_ACCESS_CMD 0x75
|
|
|
|
#define IO2_CONFIGURATION_CMD 0x77
|
|
#define IO2_PIN_ACCESS_CMD 0x7A
|
|
|
|
#define I2C_ADDRESS_CMD 0x7C
|
|
|
|
#define ID_EEPROM_WRITE_PROTECT_CTRL_CMD 0x7E
|
|
#define ID_EEPROM_ADDRESS_CMD 0x7F
|
|
|
|
#define RTC_TIME_CMD 0xB0
|
|
#define RTC_ALARM_CMD 0xB9
|
|
#define RTC_CTRL_STATUS_CMD 0xC2
|
|
|
|
#define RESET_TO_DEFAULT_CMD 0xF0
|
|
#define FIRMWARE_VERSION_CMD 0xFD
|
|
|
|
#define BATT_NORMAL 0
|
|
#define BATT_CHARGING_FROM_IN 1
|
|
#define BATT_CHARGING_FROM_5V 2
|
|
#define BATT_NOT_PRESENT 3
|
|
|
|
#define POWER_NOT_PRESENT 0
|
|
#define POWER_BAD 1
|
|
#define POWER_WEAK 2
|
|
#define POWER_PRESENT 3
|
|
|
|
#define LOW_BATTERY_THRESHOLD 25.0
|
|
#define HIGH_BATTERY_THRESHOLD 75.0
|
|
#define NOMINAL_BATTERY_VOLTAGE 4.18
|
|
|
|
#define DRIVER_NAME "PiJuice UPS driver"
|
|
#define DRIVER_VERSION "0.10"
|
|
|
|
static uint8_t i2c_address = 0x14;
|
|
static uint8_t shutdown_delay = 30;
|
|
|
|
/*
|
|
* Flags used to indicate a change in power status
|
|
*/
|
|
static uint8_t usb_power = 0;
|
|
static uint8_t gpio_power = 0;
|
|
static uint8_t battery_power = 0;
|
|
|
|
/*
|
|
* Smooth out i2c read errors by holding the most recent
|
|
* battery charge level reading
|
|
*/
|
|
static float battery_charge_level = 0;
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Andrew Anderson <aander07@gmail.com>",
|
|
DRV_EXPERIMENTAL,
|
|
{ NULL }
|
|
};
|
|
|
|
/* The macros below all write into a "data" variable defined by the routine
|
|
* scope which calls them, with respective type of uint8_t for "byte" and
|
|
* uint16_t for "word" macros. Native i2c functions operate with __s32 type
|
|
* (currently, signed 32-bit ints?) with negative values for error returns.
|
|
* Note: some manpages refer to "s32" while headers on my and CI systems use
|
|
* a "__s32" type. Maybe this is something to determine in configure script?
|
|
* Code below was fixed to convert the valid values and avoid compiler
|
|
* warnings about comparing whether unsigned ints happened to be negative.
|
|
*/
|
|
#define I2C_READ_BYTE(fd, cmd, label) \
|
|
{ \
|
|
__s32 sData; \
|
|
if ((sData = i2c_smbus_read_byte_data(upsfd, cmd)) < 0 ) { \
|
|
upsdebugx(2, "Failure reading the i2c bus [%s]", label); \
|
|
return; \
|
|
} ; \
|
|
data = (uint8_t) sData; \
|
|
}
|
|
|
|
#define I2C_WRITE_BYTE(fd, cmd, value, label) \
|
|
{ \
|
|
if ( i2c_smbus_write_byte_data(upsfd, cmd, value) < 0 ) { \
|
|
upsdebugx(2, "Failure writing to the i2c bus [%s]", label); \
|
|
return; \
|
|
} ; \
|
|
}
|
|
|
|
#define I2C_READ_WORD(fd, cmd, label) \
|
|
{ \
|
|
__s32 sData; \
|
|
if ((sData = i2c_smbus_read_word_data(upsfd, cmd)) < 0 ) { \
|
|
upsdebugx(2, "Failure reading the i2c bus [%s]", label); \
|
|
return; \
|
|
} ; \
|
|
data = (uint16_t) sData; \
|
|
}
|
|
|
|
#define I2C_READ_BLOCK(fd, cmd, size, block, label) \
|
|
if ((i2c_smbus_read_i2c_block_data(upsfd, cmd, size, block)) < 0 ) { \
|
|
upsdebugx(2, "Failure reading the i2c bus [%s]", label); \
|
|
return; \
|
|
}
|
|
|
|
static inline int open_i2c_bus(char *path, uint8_t addr)
|
|
{
|
|
int file;
|
|
|
|
if ((file = open(path, O_RDWR)) < 0)
|
|
{
|
|
fatal_with_errno(EXIT_FAILURE, "Failed to open the i2c bus on %s", path);
|
|
}
|
|
|
|
if (ioctl(file, I2C_SLAVE, addr) < 0)
|
|
{
|
|
fatal_with_errno(EXIT_FAILURE, "Failed to acquire the i2c bus and/or talk to the UPS");
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
static void get_charge_level_hi_res()
|
|
{
|
|
uint8_t cmd = CHARGE_LEVEL_HI_RES_CMD;
|
|
uint16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_WORD( upsfd, cmd, __func__ )
|
|
|
|
/*
|
|
* Use an external variable to allow for missed i2c bus
|
|
* reads; the charge level data may be slightly stale,
|
|
* but no other options seem reasonable:
|
|
*
|
|
* 1) store 0
|
|
* Leads to a false report of a depleted battery, possibly
|
|
* triggering an immediate shutdown if on battery power only
|
|
* 2) store -1
|
|
* Adds a lot of logic to "skip" over negative charge levels,
|
|
* which effectively accomplishes the same thing
|
|
* 3) retry the read immediately
|
|
* Could tie up the i2c bus and make matters exponentially worse
|
|
*/
|
|
battery_charge_level = data / 10.0;
|
|
|
|
upsdebugx( 1, "Battery Charge Level: %02.1f%%", battery_charge_level );
|
|
dstate_setinfo( "battery.charge", "%02.1f", battery_charge_level );
|
|
}
|
|
|
|
static void get_status()
|
|
{
|
|
uint8_t cmd = STATUS_CMD;
|
|
uint8_t data;
|
|
char status_buf[ST_MAX_VALUE_LEN];
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
memset( status_buf, 0, ST_MAX_VALUE_LEN );
|
|
|
|
I2C_READ_BYTE( upsfd, cmd, __func__ )
|
|
|
|
uint8_t batteryStatus = data >> 2 & 0x03;
|
|
switch( batteryStatus )
|
|
{
|
|
case BATT_NORMAL:
|
|
upsdebugx( 1, "Battery Status: Normal" );
|
|
dstate_setinfo( "battery.packs", "%d", 1 );
|
|
dstate_setinfo( "battery.packs.bad", "%d", 0 );
|
|
break;
|
|
case BATT_CHARGING_FROM_IN:
|
|
upsdebugx( 1, "Battery Status: Charging from IN" );
|
|
dstate_setinfo( "battery.packs", "%d", 1 );
|
|
dstate_setinfo( "battery.packs.bad", "%d", 0 );
|
|
break;
|
|
case BATT_CHARGING_FROM_5V:
|
|
upsdebugx( 1, "Battery Status: Charging from 5V" );
|
|
dstate_setinfo( "battery.packs", "%d", 1 );
|
|
dstate_setinfo( "battery.packs.bad", "%d", 0 );
|
|
break;
|
|
case BATT_NOT_PRESENT:
|
|
upsdebugx( 1, "Battery Status: Not Present" );
|
|
dstate_setinfo( "battery.packs", "%d", 0 );
|
|
dstate_setinfo( "battery.packs.bad", "%d", 1 );
|
|
break;
|
|
default:
|
|
upsdebugx( 1, "battery.status: UNKNOWN" );
|
|
}
|
|
|
|
uint8_t powerInput = data >> 4 & 0x03;
|
|
switch( powerInput )
|
|
{
|
|
case POWER_NOT_PRESENT:
|
|
upsdebugx( 1, "Power Input: Not Present" );
|
|
break;
|
|
case POWER_BAD:
|
|
upsdebugx( 1, "Power Input: Bad" );
|
|
break;
|
|
case POWER_WEAK:
|
|
upsdebugx( 1, "Power Input: Weak" );
|
|
break;
|
|
case POWER_PRESENT:
|
|
upsdebugx( 1, "Power Input: Present" );
|
|
break;
|
|
default:
|
|
upsdebugx( 1, "Power Input: UNKNOWN" );
|
|
}
|
|
|
|
uint8_t powerInput5vIo = data >> 6 & 0x03;
|
|
switch( powerInput5vIo )
|
|
{
|
|
case POWER_NOT_PRESENT :
|
|
upsdebugx(1, "Power Input 5v: Not Present");
|
|
break;
|
|
case POWER_BAD:
|
|
upsdebugx(1, "Power Input 5v: Bad");
|
|
break;
|
|
case POWER_WEAK:
|
|
upsdebugx(1, "Power Input 5v: Weak");
|
|
break;
|
|
case POWER_PRESENT:
|
|
upsdebugx(1, "Power Input 5v: Present");
|
|
break;
|
|
default:
|
|
upsdebugx(1, "Power Input 5v: UNKNOWN");
|
|
}
|
|
|
|
if ( batteryStatus == BATT_NORMAL ||
|
|
batteryStatus == BATT_CHARGING_FROM_IN ||
|
|
batteryStatus == BATT_CHARGING_FROM_5V )
|
|
{
|
|
get_charge_level_hi_res();
|
|
|
|
if ( battery_charge_level <= LOW_BATTERY_THRESHOLD )
|
|
{
|
|
upsdebugx( 1, "Battery Charge Status: LOW" );
|
|
snprintfcat( status_buf, ST_MAX_VALUE_LEN, "LB " );
|
|
}
|
|
else if ( battery_charge_level > HIGH_BATTERY_THRESHOLD )
|
|
{
|
|
upsdebugx( 1, "Battery Charge Status: HIGH" );
|
|
snprintfcat( status_buf, ST_MAX_VALUE_LEN, "HB " );
|
|
}
|
|
}
|
|
else if ( batteryStatus == BATT_NOT_PRESENT )
|
|
{
|
|
snprintfcat( status_buf, ST_MAX_VALUE_LEN, "RB " );
|
|
}
|
|
|
|
if ( batteryStatus <= BATT_NOT_PRESENT &&
|
|
powerInput <= POWER_PRESENT &&
|
|
powerInput5vIo <= POWER_PRESENT )
|
|
{
|
|
if ( powerInput == POWER_NOT_PRESENT &&
|
|
( powerInput5vIo != POWER_NOT_PRESENT ))
|
|
{
|
|
if ( usb_power != 1 || gpio_power != 0 )
|
|
{
|
|
upslogx( LOG_NOTICE, "On USB power" );
|
|
}
|
|
usb_power = 1;
|
|
gpio_power = 0;
|
|
battery_power = 0;
|
|
upsdebugx( 1, "On USB power [%d:%d:%d]", usb_power, gpio_power, battery_power );
|
|
|
|
snprintfcat( status_buf, sizeof(status_buf), "OL" );
|
|
if ( batteryStatus == BATT_CHARGING_FROM_5V )
|
|
{
|
|
snprintfcat( status_buf, sizeof( status_buf ), " CHRG" );
|
|
upsdebugx( 1, "Battery Charger Status: charging" );
|
|
dstate_setinfo( "battery.charger.status", "%s", "charging" );
|
|
}
|
|
else if ( batteryStatus == BATT_NORMAL )
|
|
{
|
|
upsdebugx( 1, "Battery Charger Status: resting" );
|
|
dstate_setinfo( "battery.charger.status", "%s", "resting" );
|
|
}
|
|
status_set( status_buf );
|
|
}
|
|
else if ( powerInput5vIo == POWER_NOT_PRESENT &&
|
|
( powerInput != POWER_NOT_PRESENT &&
|
|
powerInput <= POWER_PRESENT ))
|
|
{
|
|
if ( gpio_power != 1 || usb_power != 0 )
|
|
{
|
|
upslogx( LOG_NOTICE, "On 5V_GPIO power" );
|
|
}
|
|
usb_power = 0;
|
|
gpio_power = 1;
|
|
battery_power = 0;
|
|
upsdebugx( 1, "On 5V_GPIO power [%d:%d:%d]", usb_power, gpio_power, battery_power );
|
|
|
|
snprintfcat( status_buf, sizeof(status_buf), "OL" );
|
|
if ( batteryStatus == BATT_CHARGING_FROM_IN )
|
|
{
|
|
snprintfcat( status_buf, sizeof(status_buf), " CHRG" );
|
|
status_set( status_buf );
|
|
upsdebugx( 1, "Battery Charger Status: charging" );
|
|
dstate_setinfo( "battery.charger.status", "%s", "charging" );
|
|
}
|
|
else if ( batteryStatus == BATT_NORMAL )
|
|
{
|
|
status_set( status_buf );
|
|
upsdebugx( 1, "Battery Charger Status: resting" );
|
|
dstate_setinfo( "battery.charger.status", "%s", "resting" );
|
|
}
|
|
}
|
|
else if ( ( powerInput != POWER_NOT_PRESENT && powerInput <= POWER_PRESENT ) &&
|
|
( powerInput5vIo != POWER_NOT_PRESENT && powerInput5vIo <= POWER_PRESENT ))
|
|
{
|
|
if ( usb_power != 1 || gpio_power != 1 )
|
|
{
|
|
upslogx( LOG_NOTICE, "On USB and 5V_GPIO power" );
|
|
}
|
|
usb_power = 1;
|
|
gpio_power = 1;
|
|
battery_power = 0;
|
|
upsdebugx( 1, "On USB and 5V_GPIO power [%d:%d:%d]", usb_power, gpio_power, battery_power );
|
|
|
|
snprintfcat( status_buf, sizeof( status_buf ), "OL" );
|
|
if ( batteryStatus == BATT_CHARGING_FROM_IN )
|
|
{
|
|
snprintfcat( status_buf, sizeof(status_buf), " CHRG");
|
|
status_set( status_buf );
|
|
upsdebugx( 1, "Battery Charger Status: charging" );
|
|
dstate_setinfo("battery.charger.status", "%s", "charging");
|
|
}
|
|
else if ( batteryStatus == BATT_NORMAL )
|
|
{
|
|
status_set( status_buf );
|
|
upsdebugx( 1, "Battery Charger Status: resting" );
|
|
dstate_setinfo( "battery.charger.status", "%s", "resting" );
|
|
}
|
|
}
|
|
else if ( powerInput == POWER_NOT_PRESENT && powerInput5vIo == POWER_NOT_PRESENT )
|
|
{
|
|
if ( usb_power != 0 || gpio_power != 0 )
|
|
{
|
|
upslogx( LOG_NOTICE, "On Battery power" );
|
|
}
|
|
usb_power = 0;
|
|
gpio_power = 0;
|
|
battery_power = 1;
|
|
upsdebugx( 1, "On Battery power [%d:%d:%d]", usb_power, gpio_power, battery_power );
|
|
|
|
snprintfcat( status_buf, sizeof(status_buf), "OB DISCHRG" );
|
|
status_set( status_buf );
|
|
}
|
|
}
|
|
}
|
|
|
|
static void get_battery_temperature()
|
|
{
|
|
uint8_t cmd = BATTERY_TEMPERATURE_CMD;
|
|
int16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_WORD( upsfd, cmd, __func__ )
|
|
|
|
upsdebugx( 1, "Battery Temperature: %d°C", data );
|
|
dstate_setinfo( "battery.temperature", "%d", data );
|
|
}
|
|
|
|
static void get_battery_voltage()
|
|
{
|
|
uint8_t cmd = BATTERY_VOLTAGE_CMD;
|
|
int16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_WORD( upsfd, cmd, __func__ )
|
|
|
|
upsdebugx( 1, "Battery Voltage: %0.3fV", data / 1000.0 );
|
|
dstate_setinfo( "battery.voltage", "%0.3f", data / 1000.0 );
|
|
}
|
|
|
|
static void get_battery_current()
|
|
{
|
|
uint8_t cmd = BATTERY_CURRENT_CMD;
|
|
int16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
/*
|
|
* The reported current can actually be negative, so we cannot
|
|
* check for I2C failure by looking for negative values
|
|
*/
|
|
data = i2c_smbus_read_word_data(upsfd, cmd);
|
|
|
|
if ( data & ( 1 << 15 ) )
|
|
{
|
|
data = data - ( 1 << 16 );
|
|
}
|
|
|
|
upsdebugx( 1, "Battery Current: %0.3fA", data / 1000.0 );
|
|
dstate_setinfo( "battery.current", "%0.3f", data / 1000.0 );
|
|
}
|
|
|
|
static void get_io_voltage()
|
|
{
|
|
uint8_t cmd = IO_VOLTAGE_CMD;
|
|
int16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_WORD( upsfd, cmd, __func__ )
|
|
|
|
upsdebugx( 1, "Input Voltage: %.3fV", data / 1000.0 );
|
|
dstate_setinfo( "input.voltage", "%.3f", data / 1000.0 );
|
|
}
|
|
|
|
static void get_io_current()
|
|
{
|
|
uint8_t cmd = IO_CURRENT_CMD;
|
|
int16_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
/*
|
|
* The reported current can actually be negative, so we cannot
|
|
* check for I2C failure by looking for negative values
|
|
*/
|
|
data = i2c_smbus_read_word_data(upsfd, cmd);
|
|
|
|
if ( data & ( 1 << 15 ) )
|
|
{
|
|
data = data - ( 1 << 16 );
|
|
}
|
|
|
|
upsdebugx( 1, "Input Current: %.3fA", data / 1000.0 );
|
|
dstate_setinfo( "input.current", "%.3f", data / 1000.0 );
|
|
}
|
|
|
|
static void get_firmware_version()
|
|
{
|
|
uint8_t cmd = FIRMWARE_VERSION_CMD;
|
|
uint16_t data;
|
|
uint8_t major, minor;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_WORD( upsfd, cmd, __func__ )
|
|
|
|
major = data >> 4;
|
|
minor = ( data << 4 & 0xf0 ) >> 4;
|
|
|
|
if (( major != 1 ) || ( minor > 3 ))
|
|
{
|
|
upslogx( LOG_WARNING, "Unknown Firmware release: %d.%d", major, minor );
|
|
}
|
|
|
|
upsdebugx( 1, "UPS Firmware Version: %d.%d", major, minor );
|
|
dstate_setinfo( "ups.firmware", "%d.%d", major, minor );
|
|
}
|
|
|
|
static void get_battery_profile()
|
|
{
|
|
uint8_t cmd = BATTERY_PROFILE_CMD;
|
|
__u8 block[I2C_SMBUS_BLOCK_MAX];
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_BLOCK( upsfd, cmd, 14, block, __func__ )
|
|
|
|
upsdebugx( 1, "Battery Capacity: %0.3fAh", ( block[1] << 8 | block[0] ) / 1000.0 );
|
|
dstate_setinfo( "battery.capacity", "%0.3f", ( block[1] << 8 | block[0] ) / 1000.0 );
|
|
}
|
|
|
|
static void get_battery_profile_ext()
|
|
{
|
|
uint8_t cmd = BATTERY_EXT_PROFILE_CMD;
|
|
__u8 block[I2C_SMBUS_BLOCK_MAX];
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_BLOCK( upsfd, cmd, 17, block, __func__ )
|
|
|
|
switch( block[0] & 0xFF00 )
|
|
{
|
|
case 0:
|
|
upsdebugx( 1, "Battery Chemistry: LiPO" );
|
|
dstate_setinfo( "battery.type", "%s", "LiPO" );
|
|
break;
|
|
case 1:
|
|
upsdebugx( 1, "Battery Chemistry: LiFePO4" );
|
|
dstate_setinfo( "battery.type", "%s", "LiFePO4" );
|
|
break;
|
|
default:
|
|
upsdebugx( 1, "Battery Chemistry: UNKNOWN" );
|
|
dstate_setinfo( "battery.type", "%s", "UNKNOWN" );
|
|
}
|
|
}
|
|
|
|
static void get_power_off()
|
|
{
|
|
uint8_t cmd = POWER_OFF_CMD;
|
|
uint8_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_BYTE( upsfd, cmd, __func__ )
|
|
|
|
if ( data == 255 )
|
|
{
|
|
upsdebugx( 1, "Power Off: DISABLED" );
|
|
}
|
|
else if ( data <= 250 )
|
|
{
|
|
upsdebugx( 1, "Power Off: %d Seconds", data );
|
|
}
|
|
}
|
|
|
|
static void set_power_off()
|
|
{
|
|
uint8_t cmd = POWER_OFF_CMD;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
/*
|
|
* Acceptable values for shutdown_delay are 1-250,
|
|
* use 0/255 to clear a scheduled power off command
|
|
*/
|
|
|
|
if ( shutdown_delay > 250 )
|
|
{
|
|
upslogx(
|
|
LOG_WARNING,
|
|
"shutdown delay of >250 seconds requested, shortening to 250 seconds"
|
|
);
|
|
shutdown_delay = 250;
|
|
}
|
|
|
|
if ( shutdown_delay == 0 )
|
|
{
|
|
upslogx(
|
|
LOG_WARNING,
|
|
"shutdown delay of 0 seconds requested, using 1 second instead"
|
|
);
|
|
shutdown_delay = 1;
|
|
}
|
|
|
|
I2C_WRITE_BYTE( upsfd, cmd, shutdown_delay, __func__ )
|
|
}
|
|
|
|
static void get_time()
|
|
{
|
|
uint8_t cmd = RTC_TIME_CMD;
|
|
__u8 block[I2C_SMBUS_BLOCK_MAX];
|
|
uint8_t second, minute, hour, day, month, subsecond;
|
|
uint16_t year;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_BLOCK( upsfd, cmd, 9, block, __func__ )
|
|
|
|
second = (( (block[0] >> 4 ) & 0x07) * 10 ) + ( block[0] & 0x0F );
|
|
minute = (( (block[1] >> 4 ) & 0x07) * 10 ) + ( block[1] & 0x0F );
|
|
hour = (( (block[2] >> 4 ) & 0x03) * 10 ) + ( block[2] & 0x0F );
|
|
day = (( (block[4] >> 4 ) & 0x03) * 10 ) + ( block[4] & 0x0F );
|
|
month = (( (block[5] >> 4 ) & 0x01) * 10 ) + ( block[5] & 0x0F );
|
|
year = (( (block[6] >> 4 ) & 0x0F) * 10 ) + ( block[6] & 0x0F ) + 2000;
|
|
subsecond = block[7] * 100 / 256;
|
|
|
|
upsdebugx( 1, "UPS Time: %02d:%02d:%02d.%02d", hour, minute, second, subsecond );
|
|
dstate_setinfo( "ups.time", "%02d:%02d:%02d.%02d", hour, minute, second, subsecond );
|
|
|
|
upsdebugx( 1, "UPS Date: %04d-%02d-%02d", year, month, day );
|
|
dstate_setinfo( "ups.date", "%04d-%02d-%02d", year, month, day );
|
|
}
|
|
|
|
static void get_i2c_address()
|
|
{
|
|
uint8_t cmd = I2C_ADDRESS_CMD;
|
|
uint8_t data;
|
|
|
|
upsdebugx( 3, __func__ );
|
|
|
|
I2C_READ_BYTE( upsfd, cmd, __func__ )
|
|
|
|
upsdebugx( 1, "I2C Address: 0x%0x", data );
|
|
|
|
if ( data == i2c_address )
|
|
{
|
|
upsdebugx( 1, "Found device '0x%0x' on port '%s'",
|
|
(unsigned int) i2c_address, device_path );
|
|
}
|
|
else
|
|
{
|
|
fatalx( EXIT_FAILURE,
|
|
"Could not find PiJuice HAT at I2C address 0x%0x",
|
|
i2c_address );
|
|
}
|
|
}
|
|
|
|
void upsdrv_initinfo(void)
|
|
{
|
|
|
|
dstate_setinfo( "ups.mfr", "%s", "PiJuice" );
|
|
dstate_setinfo( "ups.type", "%s", "HAT" );
|
|
|
|
/* note: for a transition period, these data are redundant */
|
|
|
|
dstate_setinfo( "device.mfr", "%s", "PiJuice" );
|
|
dstate_setinfo( "device.type", "%s", "HAT" );
|
|
|
|
upsdebugx( 1, "Low Battery Threshold: %0.0f%%", LOW_BATTERY_THRESHOLD );
|
|
dstate_setinfo( "battery.charge.low", "%0.0f", LOW_BATTERY_THRESHOLD );
|
|
|
|
upsdebugx( 1, "Nominal Battery Voltage: %0.3fV", NOMINAL_BATTERY_VOLTAGE );
|
|
dstate_setinfo( "battery.voltage.nominal", "%0.3f", NOMINAL_BATTERY_VOLTAGE );
|
|
|
|
get_i2c_address();
|
|
get_battery_profile();
|
|
get_battery_profile_ext();
|
|
}
|
|
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
status_init();
|
|
|
|
get_status();
|
|
get_battery_temperature();
|
|
get_battery_voltage();
|
|
get_battery_current();
|
|
get_io_voltage();
|
|
get_io_current();
|
|
get_time();
|
|
get_power_off();
|
|
|
|
status_commit();
|
|
dstate_dataok();
|
|
}
|
|
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
set_power_off();
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
printf("\nThe default I2C address is 20 [0x14]\n");
|
|
printf("\n");
|
|
}
|
|
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
addvar(VAR_VALUE, "i2c_address", "Override i2c address setting");
|
|
}
|
|
|
|
void upsdrv_initups(void)
|
|
{
|
|
upsfd = open_i2c_bus( device_path, i2c_address );
|
|
|
|
/* probe ups type */
|
|
get_firmware_version();
|
|
|
|
/* get variables and flags from the command line */
|
|
|
|
if (getval("i2c_address"))
|
|
i2c_address = atoi(getval("i2c_address"));
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
close(upsfd);
|
|
}
|