On 18/06/2014 04:17, Charles Lepple wrote:
On Jun 13, 2014, at 2:53 AM, Giuseppe Corbelli 
<[email protected]> wrote:

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.

Hi Giuseppe,

I am not an autoconf expert, so I will admit that my first inclination was
to try and build it on a number of different boxes, and see what breaks. I
don’t see any red flags at the moment.

However, the patch file seems malformed - I got errors when passing it to “git 
apply”.

Did the patch file get edited after you generated it? What version did you 
branch from?

Yes, I edited to avoid patching the .in generated files. Must have done something wrong.
Nevermind, please try the one attached, just created by

git diff -p 80d9534 configure.ac data/driver.list.in drivers/Makefile.am drivers/asem.c > asem.patch

Just applied it on current master (80d9534a133da170cade150700407920450a9753) and seems ok.

--
            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 3833ab9..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"
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

Reply via email to