# $Id$
#
# TODO:
package main;
use strict;
use warnings;
use SetExtensions;
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
#=======================================================================================
sub CustomReadings_Initialize($) {
my ($hash) = @_;
$hash->{DefFn} = "CustomReadings_Define";
$hash->{UndefFn} = "CustomReadings_Undef";
$hash->{SetFn} = "CustomReadings_Set";
$hash->{AttrList} = "readingDefinitions "
. "interval "
. "$readingFnAttributes";
}
#=======================================================================================
sub CustomReadings_Define($$) {
my ($hash, $def) = @_;
my $name = $hash->{NAME};
CustomReadings_OnTimer($hash);
return undef;
}
#=======================================================================================
sub CustomReadings_OnTimer($) {
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+ AttrVal( $name, "interval", 5), "CustomReadings_OnTimer", $hash, 0);
CustomReadings_Read($hash);
}
#=======================================================================================
sub CustomReadings_Read($) {
my ($hash) = @_;
my $name = $hash->{NAME};
# Get the readingDefinitions and remove all newlines from the attribute
my $readingDefinitions = AttrVal( $name, "readingDefinitions", "");
$readingDefinitions =~ s/\n//g;
my @used = ("state");
my $isCombined = 0;
my @combinedOutput = ();
my $hasErrors = 0;
readingsBeginUpdate($hash);
my @definitionList = split(",", $readingDefinitions);
while (@definitionList) {
my $param = shift(@definitionList);
while ($param && $param =~ /{/ && $param !~ /}/ ) {
my $next = shift(@definitionList);
last if( !defined($next) );
$param .= ",". $next;
}
my @definition = split(':', $param, 2);
if ($definition[0] eq "COMBINED") {
$isCombined = 1;
my $cmdStr = $definition[1];
my $cmdTemp = eval("$cmdStr");
if (ref $cmdTemp eq 'ARRAY') {
@combinedOutput = @{ $cmdTemp };
} else {
@combinedOutput = split(/^/, $cmdTemp);
}
Log 5, "Using combined mode for customReadings: $cmdStr";
next;
}
else {
push(@used, $definition[0]);
}
if($definition[1] ne "") {
$isCombined = 0;
}
my $value;
if ($isCombined) {
$value = shift @combinedOutput;
if (!($value)) {
$value = 0;
$hasErrors = 1;
Log 3, "customReadings: Warning for $name: combined command for " . $definition[0] . " returned nothing or not enough lines.";
}
}
else {
$value = eval($definition[1]);
}
if(defined $value) {
$value =~ s/^\s+|\s+$//g;
}
else {
$value = "ERROR";
$hasErrors = 1;
}
readingsBulkUpdate($hash, $definition[0], $value);
}
readingsBulkUpdate($hash, "state", $hasErrors ? "Errors" : "OK");
readingsEndUpdate($hash, 1);
foreach my $r (keys %{$hash->{READINGS}}) {
if (not $r ~~ @used) {
delete $hash->{READINGS}{$r};
}
}
}
#=======================================================================================
sub CustomReadings_Undef($$) {
my ($hash, $arg) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
return undef;
}
#=======================================================================================
sub CustomReadings_GetHTML ($) {
my ($name) = @_;
my $hash = $main::defs{$name};
my $result = "";
$result .= "
";
my @sortedReadings = sort keys %{$hash->{READINGS}};
foreach my $reading (@sortedReadings) {
$result .= "";
$result .= "$reading: | " . ReadingsVal($name, $reading, "???") . " | ";
$result .= "
";
}
$result .= "
";
return $result;
}
#=======================================================================================
sub CustomReadings_Set($@) {
my ($hash, @a) = @_;
my $name = shift @a;
my $cmd = shift @a;
my $arg = join(" ", @a);
my $list = "update";
return $list if( $cmd eq '?' || $cmd eq '');
if ($cmd eq "update") {
CustomReadings_Read($hash);
}
else {
return "Unknown argument $cmd, choose one of ".$list;
}
return undef;
}
1;
=pod
=item summary Allows to define own readings.
=item summary_DE Ermöglicht eingen readings.
=begin html
CustomReadings
FHEM module to define own readings.
This module allows to define own readings. The readings can be defined in an attribute so that they can get changed without changing the code of the module.
To use this module you should have some perl and linux knowledge
The examples presuppose that you run FHEM on a linux machine like a Raspberry Pi or a Cubietruck.
Note: the "bullshit" definition is an example to show what happens if you define bullshit :-)
Example (definition in fhem.cfg)
define myReadings CustomReadings
attr myReadings room 0-Test
attr myReadings group Readings
attr myReadings interval 2
attr myReadings readingDefinitions hdd_temperature:qx(hddtemp /dev/sda 2>&1),
ac_powersupply_voltage:qx(cat /sys/class/power_supply/ac/voltage_now 2>&1) / 1000000,
ac_powersupply_current:qx(cat /sys/class/power_supply/ac/current_now 2>&1) / 1000000,
perl_version:$],
timezone:qx(cat /etc/timezone 2>&1),
kernel:qx(uname -r 2>&1),
device_name:$hash->{NAME},
bullshit: $hash->{bullshit},
fhem_backup_folder_size:qx(du -ch /opt/fhem/backup | grep total | cut -d 't' -f1 2>&1)
Optionally, to display the readings:
define myReadingsDisplay weblink htmlCode {CustomReadings_GetHTML('myReadings')}
attr myReadingsDisplay group Readings
attr myReadingsDisplay room 0-Test
Resulting readings:
ac_powersupply_current |
0.236 |
2014-08-09 15:40:21 |
ac_powersupply_voltage |
5.028 |
2014-08-09 15:40:21 |
bullshit |
ERROR |
2014-08-09 15:40:21 |
device_name |
myReadings |
2014-08-09 15:40:21 |
fhem_backup_folder_size |
20M |
2014-08-09 15:40:21 |
hdd_temperature |
/dev/sda: TS128GSSD320: 47°C |
2014-08-09 15:40:21 |
kernel |
3.4.103-sun7i+ |
2014-08-09 15:40:21 |
perl_version |
5.014002 |
2014-08-09 15:40:21 |
timezone |
Europe/Berlin |
2014-08-09 15:40:21 |
Define
define <name> CustomReadings
Readings
As defined
Attributes
- interval
Refresh interval in seconds
- readingDefinitions
The definitions are separated by a comma. A definition consists of two parts, separated by a colon.
The first part is the name of the reading and the second part the function.
The function gets evaluated and must return a result.
Example: kernel:qx(uname -r 2>&1)
Defines a reading with the name "kernel" and evaluates the linux function uname -r
Multiline output from commands, systemcall, scripts etc. can be use for more than one reading with
the keyword COMBINED
as reading (which wont appear itself) while its command output
will be put line by line in the following readings defined (so they don't need a function defined
after the colon (it would be ignored)).But the lines given must match the number and order of the
following readings.
COMBINED can be used together or lets say after or even in between normal expressions if the
number of lines of the output matches exactly.
Example: COMBINED:qx(cat /proc/sys/vm/dirty_background*),dirty_bytes:,dirty_ration:
Defines two readings (dirty_bytes and dirty_ratio) which will get set by the lines of those
two files the cat command will find in the kernel proc directory.
In some cases this can give an noticeable performance boost as the readings are filled up all at once.
=end html
=cut