mirror of
https://gitlab.freedesktop.org/upower/upower.git
synced 2026-01-28 11:50:26 +01:00
439 lines
11 KiB
C
439 lines
11 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2006-2008 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* Data values taken from wattsup.c: Copyright (C) 2005 Patrick Mochel
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib-object.h>
|
|
#include <gudev/gudev.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <errno.h>
|
|
|
|
#include "up-types.h"
|
|
#include "up-device-wup.h"
|
|
|
|
#define UP_DEVICE_WUP_REFRESH_TIMEOUT 10 /* seconds */
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_WATTS 0x0
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_VOLTS 0x1
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_AMPS 0x2
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_KWH 0x3
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_COST 0x4
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MONTHLY_KWH 0x5
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MONTHLY_COST 0x6
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MAX_WATTS 0x7
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MAX_VOLTS 0x8
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MAX_AMPS 0x9
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MIN_WATTS 0xa
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MIN_VOLTS 0xb
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_MIN_AMPS 0xc
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_POWER_FACTOR 0xd
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_DUTY_CYCLE 0xe
|
|
#define UP_DEVICE_WUP_RESPONSE_OFFSET_POWER_CYCLE 0xf
|
|
|
|
/* commands can never be bigger then this */
|
|
#define UP_DEVICE_WUP_COMMAND_LEN 256
|
|
|
|
struct UpDeviceWupPrivate
|
|
{
|
|
int fd;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (UpDeviceWup, up_device_wup, UP_TYPE_DEVICE)
|
|
|
|
static gboolean up_device_wup_refresh (UpDevice *device, UpRefreshReason reason);
|
|
|
|
/**
|
|
* up_device_wup_set_speed:
|
|
**/
|
|
static gboolean
|
|
up_device_wup_set_speed (UpDeviceWup *wup)
|
|
{
|
|
struct termios t;
|
|
int retval;
|
|
|
|
retval = tcgetattr (wup->priv->fd, &t);
|
|
if (retval != 0) {
|
|
g_debug ("failed to get speed");
|
|
return FALSE;
|
|
}
|
|
|
|
cfmakeraw (&t);
|
|
cfsetispeed (&t, B115200);
|
|
cfsetospeed (&t, B115200);
|
|
tcflush (wup->priv->fd, TCIFLUSH);
|
|
|
|
t.c_iflag |= IGNPAR;
|
|
t.c_cflag &= ~CSTOPB;
|
|
retval = tcsetattr (wup->priv->fd, TCSANOW, &t);
|
|
if (retval != 0) {
|
|
g_debug ("failed to set speed");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_write_command:
|
|
*
|
|
* data: a command string in the form "#command,subcommand,datalen,data[n]", e.g. "#R,W,0"
|
|
**/
|
|
static gboolean
|
|
up_device_wup_write_command (UpDeviceWup *wup, const gchar *data)
|
|
{
|
|
guint ret = TRUE;
|
|
gint retval;
|
|
gint length;
|
|
|
|
length = strlen (data);
|
|
g_debug ("writing [%s]", data);
|
|
retval = write (wup->priv->fd, data, length);
|
|
if (retval != length) {
|
|
g_debug ("Writing [%s] to device failed", data);
|
|
ret = FALSE;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_read_command:
|
|
*
|
|
* Return value: Some data to parse
|
|
**/
|
|
static gchar *
|
|
up_device_wup_read_command (UpDeviceWup *wup)
|
|
{
|
|
int retval;
|
|
gchar buffer[UP_DEVICE_WUP_COMMAND_LEN];
|
|
retval = read (wup->priv->fd, &buffer, UP_DEVICE_WUP_COMMAND_LEN);
|
|
if (retval < 0) {
|
|
g_debug ("failed to read from fd: %s", strerror (errno));
|
|
return NULL;
|
|
}
|
|
return g_strdup (buffer);
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_parse_command:
|
|
*
|
|
* Return value: Som data to parse
|
|
**/
|
|
static gboolean
|
|
up_device_wup_parse_command (UpDeviceWup *wup, const gchar *data)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gchar command;
|
|
gchar subcommand;
|
|
gchar **tokens = NULL;
|
|
gchar *packet = NULL;
|
|
guint i;
|
|
guint size;
|
|
guint length;
|
|
guint number_tokens;
|
|
UpDevice *device = UP_DEVICE (wup);
|
|
const guint offset = 3;
|
|
|
|
/* invalid */
|
|
if (data == NULL)
|
|
goto out;
|
|
|
|
/* Try to find a valid packet in the data stream
|
|
* Data may be sdfsd#P,-,0;sdfs and we only want this bit:
|
|
* \-----/
|
|
* so try to find the start and the end */
|
|
|
|
/* ensure we have a long enough response */
|
|
length = strlen (data);
|
|
if (length < 3) {
|
|
g_debug ("not enough data '%s'", data);
|
|
goto out;
|
|
}
|
|
|
|
/* strip to the first '#' char */
|
|
for (i=0; i<length-1; i++)
|
|
if (data[i] == '#')
|
|
packet = g_strdup (data+i);
|
|
|
|
/* does packet exist? */
|
|
if (packet == NULL) {
|
|
g_debug ("no start char in %s", data);
|
|
goto out;
|
|
}
|
|
|
|
/* replace the first ';' char with a NULL if it exists */
|
|
length = strlen (packet);
|
|
for (i=0; i<length; i++) {
|
|
if (packet[i] < 0x20 || packet[i] > 0x7e)
|
|
packet[i] = '?';
|
|
if (packet[i] == ';') {
|
|
packet[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* check we have enough data inthe packet */
|
|
tokens = g_strsplit (packet, ",", -1);
|
|
number_tokens = g_strv_length (tokens);
|
|
if (number_tokens < 3) {
|
|
g_debug ("not enough tokens '%s'", packet);
|
|
goto out;
|
|
}
|
|
|
|
/* remove leading or trailing whitespace in tokens */
|
|
for (i=0; i<number_tokens; i++)
|
|
g_strstrip (tokens[i]);
|
|
|
|
/* check the first token */
|
|
length = strlen (tokens[0]);
|
|
if (length != 2) {
|
|
g_debug ("expected command '#?' but got '%s'", tokens[0]);
|
|
goto out;
|
|
}
|
|
if (tokens[0][0] != '#') {
|
|
g_debug ("expected command '#?' but got '%s'", tokens[0]);
|
|
goto out;
|
|
}
|
|
command = tokens[0][1];
|
|
|
|
/* check the second token */
|
|
length = strlen (tokens[1]);
|
|
if (length != 1) {
|
|
g_debug ("expected command '?' but got '%s'", tokens[1]);
|
|
goto out;
|
|
}
|
|
subcommand = tokens[1][0]; /* expect to be '-' */
|
|
|
|
/* check the length is present */
|
|
length = strlen (tokens[2]);
|
|
if (length == 0) {
|
|
g_debug ("length value not present");
|
|
goto out;
|
|
}
|
|
|
|
/* check the length matches what data we've got*/
|
|
size = atoi (tokens[2]);
|
|
if (size != number_tokens - offset) {
|
|
g_debug ("size expected to be '%i' but got '%i'", number_tokens - offset, size);
|
|
goto out;
|
|
}
|
|
|
|
/* update the command fields */
|
|
if (command == 'd' && subcommand == '-' && number_tokens - offset == 18) {
|
|
g_object_set (device,
|
|
"energy-rate", strtod (tokens[offset+UP_DEVICE_WUP_RESPONSE_OFFSET_WATTS], NULL) / 10.0f,
|
|
"voltage", strtod (tokens[offset+UP_DEVICE_WUP_RESPONSE_OFFSET_VOLTS], NULL) / 10.0f,
|
|
NULL);
|
|
ret = TRUE;
|
|
} else {
|
|
g_debug ("ignoring command '%c'", command);
|
|
}
|
|
|
|
out:
|
|
g_free (packet);
|
|
g_strfreev (tokens);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_coldplug:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to get data and should be removed
|
|
**/
|
|
static gboolean
|
|
up_device_wup_coldplug (UpDevice *device)
|
|
{
|
|
UpDeviceWup *wup = UP_DEVICE_WUP (device);
|
|
GUdevDevice *native;
|
|
gboolean ret = FALSE;
|
|
const gchar *device_file;
|
|
const gchar *type;
|
|
gchar *data;
|
|
const gchar *vendor;
|
|
const gchar *product;
|
|
g_autofree char *serial = NULL;
|
|
|
|
/* detect what kind of device we are */
|
|
native = G_UDEV_DEVICE (up_device_get_native (device));
|
|
type = g_udev_device_get_property (native, "UP_MONITOR_TYPE");
|
|
if (type == NULL || g_strcmp0 (type, "wup") != 0)
|
|
goto out;
|
|
|
|
/* get the device file */
|
|
device_file = g_udev_device_get_device_file (native);
|
|
if (device_file == NULL) {
|
|
g_debug ("could not get device file for WUP device");
|
|
goto out;
|
|
}
|
|
|
|
/* connect to the device */
|
|
wup->priv->fd = open (device_file, O_RDWR | O_NONBLOCK);
|
|
if (wup->priv->fd < 0) {
|
|
g_debug ("cannot open device file %s", device_file);
|
|
goto out;
|
|
}
|
|
g_debug ("opened %s", device_file);
|
|
|
|
/* set speed */
|
|
ret = up_device_wup_set_speed (wup);
|
|
if (!ret) {
|
|
g_debug ("not a WUP device (cannot set speed): %s", device_file);
|
|
goto out;
|
|
}
|
|
|
|
/* attempt to clear */
|
|
ret = up_device_wup_write_command (wup, "#R,W,0;");
|
|
if (!ret)
|
|
g_debug ("failed to clear, nonfatal");
|
|
|
|
/* setup logging interval */
|
|
data = g_strdup_printf ("#L,W,3,E,1,%i;", UP_DEVICE_WUP_REFRESH_TIMEOUT);
|
|
ret = up_device_wup_write_command (wup, data);
|
|
if (!ret)
|
|
g_debug ("failed to setup logging interval, nonfatal");
|
|
g_free (data);
|
|
|
|
/* dummy read */
|
|
data = up_device_wup_read_command (wup);
|
|
g_debug ("data after clear %s", data);
|
|
|
|
/* shouldn't do anything */
|
|
up_device_wup_parse_command (wup, data);
|
|
g_free (data);
|
|
|
|
/* prefer UPOWER names */
|
|
vendor = g_udev_device_get_property (native, "UPOWER_VENDOR");
|
|
if (vendor == NULL)
|
|
vendor = g_udev_device_get_property (native, "ID_VENDOR");
|
|
product = g_udev_device_get_property (native, "UPOWER_PRODUCT");
|
|
if (product == NULL)
|
|
product = g_udev_device_get_property (native, "ID_PRODUCT");
|
|
|
|
/* hardcode some values */
|
|
serial = g_strdup (g_udev_device_get_sysfs_attr (native, "serial"));
|
|
if (serial)
|
|
g_strstrip (serial);
|
|
g_object_set (device,
|
|
"type", UP_DEVICE_KIND_MONITOR,
|
|
"is-rechargeable", FALSE,
|
|
"power-supply", FALSE,
|
|
"is-present", FALSE,
|
|
"vendor", vendor,
|
|
"model", product,
|
|
"serial", serial,
|
|
"has-history", TRUE,
|
|
"state", UP_DEVICE_STATE_DISCHARGING,
|
|
"poll-timeout", UP_DEVICE_WUP_REFRESH_TIMEOUT,
|
|
NULL);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_refresh:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to refresh or no data
|
|
**/
|
|
static gboolean
|
|
up_device_wup_refresh (UpDevice *device, UpRefreshReason reason)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gchar *data = NULL;
|
|
UpDeviceWup *wup = UP_DEVICE_WUP (device);
|
|
|
|
/* get data */
|
|
data = up_device_wup_read_command (wup);
|
|
if (data == NULL) {
|
|
g_debug ("no data");
|
|
goto out;
|
|
}
|
|
|
|
/* parse */
|
|
ret = up_device_wup_parse_command (wup, data);
|
|
if (!ret) {
|
|
g_debug ("failed to parse %s", data);
|
|
goto out;
|
|
}
|
|
|
|
/* reset time */
|
|
g_object_set (device, "update-time", (guint64) g_get_real_time () / G_USEC_PER_SEC, NULL);
|
|
|
|
out:
|
|
g_free (data);
|
|
/* FIXME: always true? */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_init:
|
|
**/
|
|
static void
|
|
up_device_wup_init (UpDeviceWup *wup)
|
|
{
|
|
wup->priv = up_device_wup_get_instance_private (wup);
|
|
wup->priv->fd = -1;
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_finalize:
|
|
**/
|
|
static void
|
|
up_device_wup_finalize (GObject *object)
|
|
{
|
|
UpDeviceWup *wup;
|
|
|
|
g_return_if_fail (object != NULL);
|
|
g_return_if_fail (UP_IS_DEVICE_WUP (object));
|
|
|
|
wup = UP_DEVICE_WUP (object);
|
|
g_return_if_fail (wup->priv != NULL);
|
|
|
|
if (wup->priv->fd > 0)
|
|
close (wup->priv->fd);
|
|
|
|
G_OBJECT_CLASS (up_device_wup_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* up_device_wup_class_init:
|
|
**/
|
|
static void
|
|
up_device_wup_class_init (UpDeviceWupClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
UpDeviceClass *device_class = UP_DEVICE_CLASS (klass);
|
|
|
|
object_class->finalize = up_device_wup_finalize;
|
|
device_class->coldplug = up_device_wup_coldplug;
|
|
device_class->refresh = up_device_wup_refresh;
|
|
}
|