As said in previous mail, I just finished a first working version of a driver
for the UPS found on ASEM PB1300 device
(http://www.asem.it/prodotti/industrial-automation/box-pcs/performance/pb1300/)
Linux only, accessed through i2c/SMBUS.
If you can spare some time, please review. Expecially my autotools skills are
ehm... a bit rusty.
--
Giuseppe Corbelli
WASP Software Engineer, Copan Italia S.p.A
Phone: +390303666318 Fax: +390302659932
E-mail: [email protected]
diff --git a/configure.ac b/configure.ac
index 00c7cff..e2cd10c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -221,7 +221,7 @@ dnl check for --with-all (or --without-all, or --with-all=auto) flag
AC_MSG_CHECKING(for --with-all)
AC_ARG_WITH(all,
- AS_HELP_STRING([--with-all], [enable serial, usb, snmp, neon, ipmi, powerman, cgi, dev, avahi]),
+ AS_HELP_STRING([--with-all], [enable serial, usb, snmp, neon, ipmi, powerman, cgi, dev, avahi, linux_i2c]),
[
if test -n "${withval}"; then
dnl Note: we allow "no" as a positive value, because
@@ -235,6 +235,7 @@ AC_ARG_WITH(all,
if test -z "${with_dev}"; then with_dev="${withval}"; fi
if test -z "${with_avahi}"; then with_avahi="${withval}"; fi
if test -z "${with_ipmi}"; then with_ipmi="${withval}"; fi
+ if test -z "${with_linux_i2c}"; then with_linux_i2c="${withval}"; fi
AC_MSG_RESULT("${withval}")
else
AC_MSG_RESULT(not given)
@@ -465,6 +466,32 @@ NUT_REPORT_FEATURE([build Mac OS X meta-driver],
[WITH_MACOSX], [Define to enable Mac OS X meta-driver])
dnl ----------------------------------------------------------------------
+dnl checks related to --with_linux_i2c
+dnl Check for i2c header on Linux, used for ASEM UPS driver
+NUT_ARG_WITH([linux_i2c], [build and install i2c drivers], [auto])
+if test "${nut_with_linux_i2c}" != no; then
+ case ${target_os} in
+ linux* )
+ AC_CHECK_HEADERS(
+ [linux/i2c-dev.h unistd.h errno.h],
+ [nut_with_linux_i2c="yes"],
+ [nut_with_linux_i2c="no"]
+ )
+ ;;
+ * )
+ nut_with_linux_i2c="no"
+ ;;
+ esac
+fi
+NUT_REPORT_FEATURE(
+ [build i2c based drivers],
+ [${nut_with_linux_i2c}],
+ [],
+ [WITH_LINUX_I2C],
+ [Define to enable I2C support]
+)
+
+dnl ----------------------------------------------------------------------
dnl Check for with-ssl, and --with-nss or --with-openssl
dnl Only one can be enabled at a time, with a preference for OpenSSL
dnl if both are available
diff --git a/data/driver.list.in b/data/driver.list.in
index 45db598..69344a3 100644
--- a/data/driver.list.in
+++ b/data/driver.list.in
@@ -91,6 +91,8 @@
"ARTronic" "ups" "2" "ARTon Platinium Combo 3.1 10/15/20 kVA" "USB" "blazer_usb"
"ARTronic" "ups" "2" "ARTon Platinium RT 1/2/3/6/10 kVA" "USB" "blazer_usb"
+"ASEM SPA" "UPS" "5" "PB1300 UPS" "UPS" "asem"
+
"ATEK" "ups" "2" "Defensor 1K Tower / Rack" "USB" "blazer_usb"
"ATEK" "ups" "2" "Defensor 2K Tower / Rack" "USB" "blazer_usb"
"ATEK" "ups" "2" "Defensor 3K Tower / Rack" "USB" "blazer_usb"
@@ -372,6 +374,8 @@
"Gemini" "ups" "1" "UPS625/UPS1000" "" "safenet"
diff --git a/drivers/Makefile.am b/drivers/Makefile.am
index be71e91..99614ca 100644
--- a/drivers/Makefile.am
+++ b/drivers/Makefile.am
@@ -44,6 +44,7 @@ USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb \
USB_DRIVERLIST = $(USB_LIBUSB_DRIVERLIST)
NEONXML_DRIVERLIST = netxml-ups
MACOSX_DRIVERLIST = macosx-ups
+LINUX_I2C_DRIVERLIST = asem
# distribute all drivers, even ones that are not built by default
EXTRA_PROGRAMS = $(SERIAL_DRIVERLIST) $(SNMP_DRIVERLIST) $(USB_DRIVERLIST) $(NEONXML_DRIVERLIST) $(MACOSX_DRIVERLIST)
@@ -74,6 +75,9 @@ endif
if WITH_MACOSX
driverexec_PROGRAMS += $(MACOSX_DRIVERLIST)
endif
+if WITH_LINUX_I2C
+ driverexec_PROGRAMS += $(LINUX_I2C_DRIVERLIST)
+endif
else
driverexec_PROGRAMS += skel
endif
@@ -219,6 +223,10 @@ macosx_ups_LDADD = $(LDADD_DRIVERS)
macosx_ups_LDFLAGS = $(LDFLAGS) -framework IOKit -framework CoreFoundation
macosx_ups_SOURCES = macosx-ups.c
+# Asem
+asem_LDADD = $(LDADD_DRIVERS)
+asem_SOURCES = asem.c
+
# nutdrv_qx USB/Serial
nutdrv_qx_SOURCES = nutdrv_qx.c
nutdrv_qx_LDADD = $(LDADD_DRIVERS) -lm
diff --git a/drivers/asem.c b/drivers/asem.c
new file mode 100644
index 0000000..625dc2e
--- /dev/null
+++ b/drivers/asem.c
@@ -0,0 +1,379 @@
+/* asem.c - driver for ASEM PB 1300 hardware, accessible through i2c.
+
+ Copyright (C) 2014 Giuseppe Corbelli <[email protected]>
+
+ 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
+
+ ASEM SPA contributed with support and documentation.
+ Copan Italia SPA funded the development.
+
+ There are 2 versions of the charger. Older one is based on Max1667,
+ newer one is a custom solution. Both are on address 0x09.
+ To be compatible with both versions just read bit 15 of address 0x13
+ to have online/on battery status.
+ Battery monitor is a BQ2060 at address 0x0B.
+
+ Beware that the SystemIO memory used by the i2c controller is reserved by ACPI.
+ On Linux, as of 3.5.x kernel only a native driver (i2c_i801) is available,
+ so you need to boot with acpi_enforce_resources=lax option.
+*/
+
+/* Assume ../include path added */
+#include <config.h>
+
+/* Depends on i2c-dev.h, Linux only */
+#if HAVE_LINUX_I2C_DEV_H
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <linux/i2c-dev.h>
+
+#include "main.h"
+
+#ifndef __STR__
+# define __STR__(x) #x
+#endif
+#ifndef __XSTR__
+# define __XSTR__(x) __STR__(x)
+#endif
+
+#define DRIVER_NAME "ASEM"
+#define DRIVER_VERSION "0.10"
+
+/* Valid on ASEM PB1300 UPS */
+#define BQ2060_ADDRESS 0x0B
+#define CHARGER_ADDRESS 0x09
+
+#define CMD_DEVICENAME 0x21
+
+#define LOW_BATTERY_THRESHOLD 25
+#define HIGH_BATTERY_THRESHOLD 75
+
+#define ACCESS_DEVICE(fd, address) \
+ if (ioctl(fd, I2C_SLAVE, address) < 0) { \
+ fatal_with_errno(EXIT_FAILURE, "Failed to acquire bus access and/or talk to slave 0x%02X", address); \
+ }
+
+static unsigned long lb_threshold = LOW_BATTERY_THRESHOLD;
+static unsigned long hb_threshold = HIGH_BATTERY_THRESHOLD;
+
+static char *valid_devicename_data[] = {
+ "ASEM SPA",
+ NULL
+};
+
+upsdrv_info_t upsdrv_info = {
+ DRIVER_NAME,
+ DRIVER_VERSION,
+ "Giuseppe Corbelli <[email protected]>",
+ DRV_EXPERIMENTAL,
+ {NULL}
+};
+
+void upsdrv_initinfo(void)
+{
+ __s32 i2c_status;
+ __u8 buffer[10];
+ unsigned short year, month, day;
+
+ ACCESS_DEVICE(upsfd, BQ2060_ADDRESS);
+
+ /* Set capacity mode in mA(h) */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x03);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read BatteryMode word data");
+ }
+ /* Clear 15th bit */
+ i2c_status = i2c_smbus_write_word_data(upsfd, 0x03, i2c_status & ~0x8000);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not set BatteryMode word data");
+ }
+
+ /* Device name */
+ memset(buffer, 0, 10);
+ i2c_status = i2c_smbus_read_block_data(upsfd, 0x21, buffer);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read DeviceName block data");
+ }
+ upsdebugx(1, "UPS model %s", (char *) buffer);
+ dstate_setinfo("ups.model", "%s", (char *) buffer);
+
+ /* Manufacturing date */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x1B);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read ManufactureDate word data");
+ }
+ /* (Year - 1980) * 512 */
+ year = (i2c_status >> 9) & 0x000000FF;
+ /* Month * 32 */
+ month = (i2c_status >> 4) & 0x0000001F;
+ day = i2c_status & 0x0000001F;
+ upsdebugx(1, "UPS manufacturing date %d-%02d-%02d (%d)", year + 1980, month, day, i2c_status);
+ dstate_setinfo("ups.mfr.date", "%d-%02d-%02d", year + 1980, month, day);
+
+ /* Device chemistry */
+ memset(buffer, 0, 10);
+ i2c_status = i2c_smbus_read_block_data(upsfd, 0x22, buffer);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read DeviceChemistry block data");
+ }
+ upsdebugx(1, "Battery chemistry %s", (char *) buffer);
+ dstate_setinfo("battery.type", "%s", (char *) buffer);
+
+ /* Serial number */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x1C);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read SerialNumber block data");
+ }
+ upsdebugx(1, "Serial Number %d", i2c_status);
+ dstate_setinfo("ups.serial", "%d", i2c_status);
+}
+
+void upsdrv_updateinfo(void)
+{
+ static char online;
+ static char discharging;
+ static char fully_charged;
+ static unsigned short charge_percentage;
+ static unsigned short voltage;
+ static unsigned short capacity;
+ static signed short current;
+ static __s32 i2c_status;
+ static __s32 temperature;
+ static __s32 runtime_to_empty;
+
+ ACCESS_DEVICE(upsfd, CHARGER_ADDRESS);
+ /* Charger only supplies online/offline status */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x13);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read charger status word at address 0x13");
+ return;
+ }
+ online = (i2c_status & 0x8000) != 0;
+ upsdebugx(3, "Charger status 0x%02X, online %d", i2c_status, online);
+
+ ACCESS_DEVICE(upsfd, BQ2060_ADDRESS);
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x16);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read bq2060 status word at address 0x16");
+ return;
+ }
+ upsdebugx(3, "bq2060 status 0x04%X", i2c_status);
+ /* Busy, leave data as stale, try next time */
+ if (i2c_status & 0x0001) {
+ dstate_datastale();
+ upslogx(LOG_NOTICE, "bq2060 is busy");
+ return;
+ }
+ /* Error, leave data as stale, try next time */
+ if (i2c_status & 0x000F) {
+ dstate_datastale();
+ upslogx(LOG_WARNING, "bq2060 returned error code 0x%02X", i2c_status & 0x000F);
+ return;
+ }
+
+ discharging = (i2c_status & 0x0040);
+ fully_charged = (i2c_status & 0x0020);
+
+ /* Charge percentage */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x0D);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read charge percentage from bq2060 at address 0x0D");
+ return;
+ }
+ charge_percentage = i2c_status & 0xFFFF;
+ upsdebugx(3, "Charge percentage %03d", charge_percentage);
+
+ /* Battery voltage in mV */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x09);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read voltage from bq2060 at address 0x09");
+ return;
+ }
+ voltage = i2c_status & 0x0000FFFF;
+ upsdebugx(3, "Battery voltage %d mV", voltage);
+
+ /* Temperature in °K */
+ temperature = i2c_smbus_read_word_data(upsfd, 0x08);
+ if (temperature == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read temperature from bq2060 at address 0x08");
+ return;
+ }
+ upsdebugx(3, "Temperature %4.1f K", temperature / 10.0);
+
+ /* Current load in mA, positive for charge, negative for discharge */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x0A);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read current from bq2060 at address 0x0A");
+ return;
+ }
+ current = i2c_status & 0x0000FFFF;
+ upsdebugx(3, "Current %d mA", current);
+
+ /* Current capacity */
+ i2c_status = i2c_smbus_read_word_data(upsfd, 0x0F);
+ if (i2c_status == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read RemainingCapacity word data");
+ return;
+ }
+ capacity = i2c_status & 0x0000FFFF;
+ upsdebugx(3, "Current capacity %d mAh", capacity);
+
+ /* Expected runtime capacity, averaged by gauge */
+ runtime_to_empty = i2c_smbus_read_word_data(upsfd, 0x12);
+ if (runtime_to_empty == -1) {
+ dstate_datastale();
+ upslogx(LOG_ERR, "Could not read AverageTimeToEmpty word data");
+ return;
+ }
+ upsdebugx(3, "Expected run-time to empty %d m", runtime_to_empty);
+
+ status_init();
+ status_set(online ? "OL" : "OB");
+ if (!discharging & !fully_charged)
+ status_set("CHRG");
+ else if (discharging && current < 0)
+ status_set("DISCHRG");
+
+ if (charge_percentage >= hb_threshold)
+ status_set("HB");
+ else if (charge_percentage <= lb_threshold)
+ status_set("LB");
+
+ /* In V */
+ dstate_setinfo("battery.voltage", "%2.3f", voltage / 1000.0);
+ /* In mAh */
+ dstate_setinfo("battery.current", "%2.3f", current / 1000.0);
+ dstate_setinfo("battery.charge", "%d", charge_percentage);
+ /* In mAh */
+ dstate_setinfo("battery.capacity", "%2.3f", capacity / 1000.0);
+ /* In °C */
+ dstate_setinfo("ups.temperature", "%4.1f", (temperature / 10.0) - 273.15);
+ /* In seconds */
+ dstate_setinfo("battery.runtime", "%d", runtime_to_empty * 60);
+ status_commit();
+ dstate_dataok();
+}
+
+void upsdrv_shutdown(void)
+{
+ /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
+
+ /* maybe try to detect the UPS here, but try a shutdown even if
+ it doesn't respond at first if possible */
+
+ /* replace with a proper shutdown function */
+ fatalx(EXIT_FAILURE, "shutdown not supported");
+
+ /* you may have to check the line status since the commands
+ for toggling power are frequently different for OL vs. OB */
+
+ /* OL: this must power cycle the load if possible */
+
+ /* OB: the load must remain off until the power returns */
+}
+
+void upsdrv_help(void)
+{
+ /* Redundant */
+ printf("\nASEM options\n");
+ printf(" HIGH/low battery thresholds\n");
+ printf(" lb = " __XSTR__(LOW_BATTERY_THRESHOLD) " (battery is low under this level)\n");
+ printf(" hb = " __XSTR__(HIGH_BATTERY_THRESHOLD) " (battery is high above this level)\n");
+}
+
+/* list flags and values that you want to receive via -x */
+void upsdrv_makevartable(void)
+{
+ addvar(VAR_VALUE, "lb", "Low battery threshold, default " __XSTR__(LOW_BATTERY_THRESHOLD));
+ addvar(VAR_VALUE, "hb", "High battery threshold, default " __XSTR__(HIGH_BATTERY_THRESHOLD));
+}
+
+void upsdrv_initups(void)
+{
+ __s32 i2c_status;
+ __u8 DeviceName_buffer[10];
+ unsigned int i;
+ unsigned long x;
+ char *DeviceName;
+ char *option;
+
+ upsfd = open(device_path, O_RDWR);
+ if (upsfd < 0) {
+ fatal_with_errno(EXIT_FAILURE, "Could not open device port '%s'", device_path);
+ }
+
+ ACCESS_DEVICE(upsfd, BQ2060_ADDRESS);
+
+ /* Get ManufacturerName */
+ memset(DeviceName_buffer, 0, 10);
+ i2c_status = i2c_smbus_read_block_data(upsfd, 0x20, DeviceName_buffer);
+ if (i2c_status == -1) {
+ fatal_with_errno(EXIT_FAILURE, "Could not read DeviceName block data");
+ }
+ i = 0;
+ while ( (DeviceName = valid_devicename_data[i++]) ) {
+ if (0 == memcmp(DeviceName, DeviceName_buffer, i2c_status))
+ break;
+ }
+ if (!DeviceName) {
+ fatal_with_errno(EXIT_FAILURE, "Device '%s' unknown", (char *) DeviceName_buffer);
+ }
+ upsdebugx(1, "Found device '%s' on port '%s'", (char *) DeviceName, device_path);
+ dstate_setinfo("ups.mfr", "%s", (char *) DeviceName);
+
+ option = getval("lb");
+ if (option) {
+ x = strtoul(option, NULL, 0);
+ if ((x == 0) && (errno != 0)) {
+ upslogx(LOG_WARNING, "Invalid value specified for low battery threshold: '%s'", option);
+ } else {
+ lb_threshold = x;
+ }
+ }
+ option = getval("hb");
+ if (option) {
+ x = strtoul(option, NULL, 0);
+ if ((x == 0) && (errno != 0)) {
+ upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s'", option);
+ } else if ((x < 1) || (x > 100)) {
+ upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s' (must be 1 < hb <= 100)", option);
+ } else {
+ hb_threshold = x;
+ }
+ }
+ /* Invalid values specified */
+ if (lb_threshold > hb_threshold) {
+ upslogx(LOG_WARNING, "lb > hb specified in options. Returning to defaults.");
+ lb_threshold = LOW_BATTERY_THRESHOLD;
+ hb_threshold = HIGH_BATTERY_THRESHOLD;
+ }
+
+ upslogx(LOG_NOTICE, "High battery threshold is %lu, low battery threshold is %lu", lb_threshold, hb_threshold);
+}
+
+void upsdrv_cleanup(void)
+{
+ close(upsfd);
+}
+#else /* HAVE_LINUX_I2C_DEV_H */
+#endif
_______________________________________________
Nut-upsdev mailing list
[email protected]
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/nut-upsdev