3898 lines
110 KiB
Perl
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# $Id$
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
use FHEM::UConv;
use Data::Dumper;
use utf8;
use Encode qw(encode_utf8 decode_utf8);
sub Unit_Initialize() {
}
# scale helper for metric numbers
my $scales_m = {
autoscale => {
'0' => { format => '%i', scale => 1000, },
'0.001' => { format => '%i', scale => 1000, },
'0.1' => { format => '%.1f', scale => 1, },
'10' => { format => '%i', scale => 1, },
'1.0e3' => { format => '%.1f', scale => 0.001, },
'2.0e3' => { format => '%i', scale => 0.001, },
'1.0e6' => { format => '%.1f', scale => 0.001, },
'2.0e6' => { format => '%i', scale => 0.001, },
},
'1.0e-12' => {
'scale_txt_m' => 'p',
'scale_txt_long_m' => {
de => 'Piko',
en => 'Pico',
},
},
'1.0e-9' => {
'scale_txt_m' => 'n',
'scale_txt_long_m' => {
de => 'Nano',
en => 'Nano',
},
},
'1.0e-6' => {
'scale_txt_m' => 'μ',
'scale_txt_long_m' => {
de => 'Mikro',
en => 'Micro',
},
},
'1.0e-3' => {
'scale_txt_m' => 'm',
'scale_txt_long_m' => {
de => 'Milli',
en => 'Mili',
},
},
'1.0e-2' => {
'scale_txt_m' => 'c',
'scale_txt_long_m' => {
de => 'Zenti',
en => 'Centi',
},
},
'1.0e-1' => {
'scale_txt_m' => 'd',
'scale_txt_long_m' => {
de => 'Dezi',
en => 'Deci',
},
},
'1.0e0' => {
'scale_txt_m' => '',
'scale_txt_long_m' => '',
},
'1.0e1' => {
'scale_txt_m' => 'da',
'scale_txt_long_m' => {
de => 'Deka',
en => 'Deca',
},
},
'1.0e2' => {
'scale_txt_m' => 'h',
'scale_txt_long_m' => {
de => 'Hekto',
en => 'Hecto',
},
},
'1.0e3' => {
'scale_txt_m' => 'k',
'scale_txt_long_m' => {
de => 'Kilo',
en => 'Kilo',
},
},
'1.0e6' => {
'scale_txt_m' => 'M',
'scale_txt_long_m' => {
de => 'Mega',
en => 'Mega',
},
},
};
# scale helper for metric square numbers
my $scales_sq = {
'scale_txt_sq' => chr(0x00B2),
'scale_txt_long_sq' => {
de => 'Quadrat',
en => 'Square',
},
};
# scale helper for metric square numbers
my $scales_cu = {
'scale_txt_cu' => chr(0x00B3),
'scale_txt_long_cu' => {
de => 'Kubik',
en => 'Cubic',
},
};
# scale helper for time related numbers
my $scales_t = {
# second
'1' => {},
# minute
'60' => {},
# hour
'3600' => {},
# day
'86400' => {},
# week
'604800' => {},
# month
'2592000' => {},
# year
'31536000' => {},
};
# scale helper for time related numbers
# Overall structure/grouping based on
# https://de.wikipedia.org/wiki/Liste_physikalischer_Gr%C3%B6%C3%9Fen
#
# Scientific range: 0 - 99
# FHEM Builtin range: 900 - 998
# FHEM User Defined range: >= 999
#
# rtype_base => reference to base rtype in $rtypes used for
# automatic unit conversion
my $rtype_base = {
0 => {
dimension => 'L',
formula_symbol => 'l',
rtype_base => 'm',
base_description => {
de => 'Länge',
en => 'length',
},
format => '%.0f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
1 => {
dimension => 'M',
formula_symbol => 'm',
rtype_base => 'kg',
base_description => {
de => 'Masse',
en => 'mass',
},
format => '%.0f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
2 => {
dimension => 'T',
formula_symbol => 't',
rtype_base => 's',
base_description => {
de => 'Zeit',
en => 'time',
},
format => '%.0f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
3 => {
dimension => 'I',
formula_symbol => 'i',
rtype_base => 'a',
base_description => {
de => 'elektrische Stromstärke',
en => 'electric current',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
4 => {
dimension => 'θ',
formula_symbol => 'T',
rtype_base => 'k',
base_description => {
de => 'absolute Temperatur',
en => 'absolute temperature',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
5 => {
dimension => 'N',
formula_symbol => 'n',
rtype_base => 'mol',
base_description => {
de => 'Stoffmenge',
en => 'amount of substance',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
6 => {
dimension => 'J',
formula_symbol => 'Iv',
rtype_base => 'cd',
base_description => {
de => 'Lichtstärke',
en => 'luminous intensity',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
7 => {
dimension => 'M L^2 T^2',
formula_symbol => 'E',
rtype_base => 'j',
base_description => {
de => 'Energie',
en => 'energy',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
8 => {
dimension => 'T^1',
formula_symbol => 'f',
rtype_base => 'hz',
base_description => {
de => 'Frequenz',
en => 'frequency',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
9 => {
dimension => 'M L^2 T^3',
formula_symbol => 'P',
rtype_base => 'w',
base_description => {
de => 'Leistung',
en => 'power',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
10 => {
dimension => 'M L^1 T^2',
formula_symbol => 'p',
rtype_base => 'pa',
base_description => {
de => 'Druck',
en => 'pressure',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
11 => {
dimension => 'M L^1 T^2',
formula_symbol => 'pabs',
rtype_base => 'pabs',
base_description => {
de => 'absoluter Druck',
en => 'absolute pressure',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
12 => {
dimension => 'M L^1 T^2',
formula_symbol => 'pamb',
rtype_base => 'pamb',
base_description => {
de => 'Luftdruck',
en => 'air pressure',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
13 => {
dimension => 'M L^2 T^3 I^1',
formula_symbol => 'U',
rtype_base => 'v',
base_description => {
de => 'elektrische Spannung',
en => 'electric voltage',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
14 => {
dimension => '1',
formula_symbol => '',
rtype_base => 'rad',
base_description => {
de => 'ebener Winkel',
en => 'plane angular',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
15 => {
dimension => 'L T^1',
formula_symbol => 'v',
rtype_base => 'kmh',
base_description => {
de => 'Geschwindigkeit',
en => 'speed',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
16 => {
dimension => 'L^2 J',
formula_symbol => 'Ev',
rtype_base => 'lx',
base_description => {
de => 'Beleuchtungsstärke',
en => 'illumination intensity',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
17 => {
dimension => 'J',
formula_symbol => 'F',
rtype_base => 'lm',
base_description => {
de => 'Lichtstrom',
en => 'luminous flux',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
18 => {
dimension => 'L^3',
formula_symbol => 'V',
rtype_base => 'm3',
base_description => {
de => 'Volumen',
en => 'volume',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
19 => {
dimension => '1',
formula_symbol => 'B',
rtype_base => 'b',
base_description => {
de => 'Logarithmische Größe',
en => 'logarithmic level',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
20 => {
dimension => 'I T',
formula_symbol => 'C',
rtype_base => 'coul',
base_description => {
de => 'elektrische Ladung',
en => 'electric charge',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
21 => {
dimension => '',
formula_symbol => 'F',
rtype_base => 'far',
base_description => {
de => 'elektrische Kapazität',
en => 'electric capacity',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
22 => {
dimension => '',
formula_symbol => 'F',
rtype_base => 'far',
base_description => {
de => 'elektrische Widerstand',
en => 'electric resistance',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
23 => {
dimension => 'L^2',
formula_symbol => 'A',
rtype_base => 'm2',
base_description => {
de => 'Flächeninhalt',
en => 'surface area',
},
format => '%i',
decimal_mark => {
de => 2,
en => 1,
},
scope => { minValue => 0 },
},
24 => {
base_description => {
de => 'Währung',
en => 'currency',
},
format => '%.2f',
decimal_mark => {
de => 2,
en => 1,
},
scope => '^[0-9]*(?:\.[0-9]*)?$',
},
25 => {
base_description => {
de => 'Zahlen',
en => 'Numbering',
},
format => '%.1f',
decimal_mark => {
de => 2,
en => 1,
},
tmpl => '%value%',
},
26 => {
base_description => {
de => 'Logische Operatoren',
en => 'Logical operators',
},
tmpl => '%value%',
},
900 => {
base_description => 'FHEM Builtin Readings Type',
tmpl => '%value%',
},
999 => {
base_description => 'FHEM User Defined Readings Type',
tmpl => '%value%',
},
};
# FHEM built-in reading types (RType).
# Values will be combined into a new super-hash using
# cross-references to $rtype_base, $scales_m, $scales_sq, $scales_cu, $scales_t
#
# ref_base => reference to $rtype_base id to include it's keys here
# ref_sq => include keys from $scales_sq here
# ref_cu => include keys from $scales_cu here
# ref_t => reference to $scales_t id to include it's keys here
# ref => self-reference to $rtype id to include it's keys here (RType alias helper)
my $rtypes = {
# others
url => {
ref_base => 900,
rtype_description => 'General URL including protocol prefix',
tmpl => '<html><a href="%value%">%value%</a></html>',
},
url_http => {
ref_base => 900,
rtype_description => 'HTTP/S URL w/ or w/o protocol prefix',
tmpl => '<html><a href="%value%">%value%</a></html>',
},
trend => {
ref_base => 900,
symbol => [ chr(0x2B0C), chr(0x2B08), chr(0x2B0A) ],
txt => [ '=', '+', '-' ],
txt_long => {
de => [ 'gleichbleibend', 'steigend', 'fallend' ],
en => [ 'steady', 'rising', 'falling' ],
fr => [ 'stable', 'croissant', 'décroissant' ],
nl => [ 'stabiel', 'stijgend', 'dalend' ],
pl => [ 'stabilne', 'rośnie', 'spada' ],
},
scope => [ '^(=|0)$', '^(\+|1)$', '^(-|2)$' ],
tmpl => '%symbol%',
tmpl_long => '%txt_long%',
rtype_description => 'Trend',
},
oknok => {
ref_base => 900,
txt => {
de => [ 'Fehler', 'ok', 'Warnung' ],
en => [ 'error', 'ok', 'warning' ],
},
scope => [
'^(nok|error|dead|invalid|0)$', '^(ok|alive|valid|1)$',
'^(warning|warn|low|2)$', '^(.*)$'
],
rtype_description => {
de =>
'Fehlerstatus; siehe RType roknok, sofern 0<>1 vertauschte Bedeutung haben',
en => 'error state',
},
},
roknok => {
ref_base => 900,
txt => {
de => [ 'Fehler', 'ok', 'Warnung' ],
en => [ 'error', 'ok', 'warning' ],
},
scope => [
'^(nok|error|dead|invalid|1)$', '^(ok|alive|valid|0)$',
'^(warning|warn|low|2)$', '^(.*)$'
],
rtype_description => {
de =>
'verdrehter Fehlerstatus, bei dem 0=ok und 1=Fehler bedeutet; Gegenteil von RType oknok',
en => 'reversed error state',
},
},
onoff => {
ref_base => 900,
txt => {
de => [ 'aus', 'an', 'nicht verfügbar' ],
en => [ 'off', 'on', 'absent' ],
},
scope =>
[ '^(off|no|standby|0)$', '^(on|yes|1)$', '^(absent|offline|2)$', ],
rtype_description => {
de => 'Schaltstatus',
en => 'Switch state',
},
},
reachable => {
ref_base => 900,
txt => {
de => [ 'nicht verfügbar', 'verfügbar' ],
en => [ 'unavailable', 'available' ],
},
scope => [
'^(unavailable|absent|disappeared|false|no|0)$',
'^(available|present|appeared|true|yes|1)$'
],
rtype_description => {
de => 'Verfügbarkeit/Erreichbarkeit',
en => 'availability/reachability',
},
},
weekday => {
ref_base => 900,
symbol => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
txt => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
txt_long => {
de => [
'Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
'Donnerstag', 'Freitag', 'Samstag'
],
en => [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'
],
},
scope => {
de => [
'^(So|Son|Sonntag|0)$', '^(Mo|Mon|Montag|1)$',
'^(Di|Die|Dienstag|2)$', '^(Mi|Mit|Mittwoch|3)$',
'^(Do|Don|Donnerstag|4)$', '^(Fr|Fre|Freitag|5)$',
'^(Sa|Sam|Samstag|6)$'
],
en => [
'^(Sun|Su|Sunday|0)$', '^(Mon|Mo|Monday|1)$',
'^(Tue|Tu|Tuesday|2)$', '^(Wed|We|Wednesday|3)$',
'^(Thu|Th|Thursday|4)$', '^(Fri|Fr|Friday|5)$',
'^(Sat|Sa|Saturday|6)$'
],
},
tmpl => '%txt%',
tmpl_long => '%txt_long%',
rtype_description => {
de =>
'Wochentag nach englisch-amerikanischer Annahme des Wochenstarts am Sonntag',
en =>
'Day of the week according to english assumption for the week to start on sunday',
},
},
weekday_iso => {
ref_base => 900,
symbol => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
txt => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
},
txt_long => {
de => [
'Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
'Donnerstag', 'Freitag', 'Samstag'
],
en => [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'
],
},
scope => {
de => [
'^(So|Son|Sonntag|6)$', '^(Mo|Mon|Montag|0)$',
'^(Di|Die|Dienstag|1)$', '^(Mi|Mit|Mittwoch|2)$',
'^(Do|Don|Donnerstag|3)$', '^(Fr|Fre|Freitag|4)$',
'^(Sa|Sam|Samstag|5)$'
],
en => [
'^(Sun|Su|Sunday|6)$', '^(Mon|Mo|Monday|0)$',
'^(Tue|Tu|Tuesday|1)$', '^(Wed|We|Wednesday|2)$',
'^(Thu|Th|Thursday|3)$', '^(Fri|Fr|Friday|4)$',
'^(Sat|Sa|Saturday|5)$'
],
},
tmpl => '%txt%',
tmpl_long => '%txt_long%',
rtype_description => {
de => 'Wochentag nach ISO-Standard, Woche beginnend am Montag',
en =>
'Day of the week according to ISO standard, week beginning on Mondays',
},
},
weekday_night => {
ref_base => 900,
symbol => {
de => [ 'So N', 'Mo N', 'Di N', 'Mi N', 'Do N', 'Fr N', 'Sa N' ],
en => [
'Sun N', 'Mon N', 'Tue N', 'Wed N', 'Thu N', 'Fri N ', 'Sat N'
],
},
txt => {
de => [ 'So N', 'Mo N', 'Di N', 'Mi N', 'Do N', 'Fr N', 'Sa N' ],
en => [
'Sun N', 'Mon N', 'Tue N', 'Wed N', 'Thu N', 'Fri N ', 'Sat N'
],
},
txt_long => {
de => [
'Sonntag Nacht',
'Montag Nacht',
'Dienstag Nacht',
'Mittwoch Nacht',
'Donnerstag Nacht',
'Freitag Nacht',
'Samstag Nacht'
],
en => [
'Sunday Night',
'Monday Night',
'Tuesday Night',
'Wednesday Night',
'Thursday Night',
'Friday Night',
'Saturday Night'
],
},
scope => {
de => [
'^(\s*(Nacht|Na)?\s*(So|Son|Sonntag|0)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Mo|Mon|Montag|1)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Di|Die|Dienstag|2)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Mi|Mit|Mittwoch|3)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Do|Don|Donnerstag|4)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Fr|Fre|Freitag|5)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Sa|Sam|Samstag|6)\s*(Nacht|Na)?\s*)$'
],
en => [
'^(\s*(Night|Na)?\s*(Sun|Su|Sunday|0)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Mon|Mo|Monday|1)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Tue|Tu|Tuesday|2)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Wed|We|Wednesday|3)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Thu|Th|Thursday|4)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Fri|Fr|Friday|5)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Sat|Sa|Saturday|6)\s*(Night|Na)?\s*)$'
],
},
tmpl => '%txt%',
tmpl_long => '%txt_long%',
rtype_description => {
de =>
'Nächtlicher Wochentag nach englisch-amerikanischem Standard des Wochenstarts am Sonntag',
en =>
'Nightly day of the week according to english standard for the week to start on sunday',
},
},
weekday_night_iso => {
ref_base => 900,
symbol => {
de => [ 'So N', 'Mo N', 'Di N', 'Mi N', 'Do N', 'Fr N', 'Sa N' ],
en => [
'Sun N', 'Mon N', 'Tue N', 'Wed N', 'Thu N', 'Fri N ', 'Sat N'
],
},
txt => {
de => [ 'So N', 'Mo N', 'Di N', 'Mi N', 'Do N', 'Fr N', 'Sa N' ],
en => [
'Sun N', 'Mon N', 'Tue N', 'Wed N', 'Thu N', 'Fri N ', 'Sat N'
],
},
txt_long => {
de => [
'Sonntag Nacht',
'Montag Nacht',
'Dienstag Nacht',
'Mittwoch Nacht',
'Donnerstag Nacht',
'Freitag Nacht',
'Samstag Nacht'
],
en => [
'Sunday Night',
'Monday Night',
'Tuesday Night',
'Wednesday Night',
'Thursday Night',
'Friday Night',
'Saturday Night'
],
},
scope => {
de => [
'^(\s*(Nacht|Na)?\s*(So|Son|Sonntag|6)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Mo|Mon|Montag|0)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Di|Die|Dienstag|1)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Mi|Mit|Mittwoch|2)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Do|Don|Donnerstag|3)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Fr|Fre|Freitag|4)\s*(Nacht|Na)?\s*)$',
'^(\s*(Nacht|Na)?\s*(Sa|Sam|Samstag|5)\s*(Nacht|Na)?\s*)$'
],
en => [
'^(\s*(Night|Na)?\s*(Sun|Su|Sunday|6)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Mon|Mo|Monday|0)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Tue|Tu|Tuesday|1)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Wed|We|Wednesday|2)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Thu|Th|Thursday|3)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Fri|Fr|Friday|4)\s*(Night|Na)?\s*)$',
'^(\s*(Night|Na)?\s*(Sat|Sa|Saturday|5)\s*(Night|Na)?\s*)$'
],
},
tmpl => '%txt%',
tmpl_long => '%txt_long%',
rtype_description => {
de =>
'Nächtlicher Wochentag nach ISO-Standard, Woche beginnend am Montag',
en =>
'Nightly day of the week according to ISO standard, week beginning on Mondays',
},
},
direction => {
ref_base => 900,
formula_symbol => 'Dir',
ref => 'gon',
scope => { minValue => 0, maxValue => 360 },
rtype_description => {
de => 'Richtungsangabe',
en => 'direction',
},
},
compasspoint => {
ref_base => 900,
formula_symbol => 'CP',
txt => {
de => [
'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
],
en => [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
],
},
txt_long => {
de => [
'Norden', 'Nord-Nordost', 'Nordost', 'Ost-Nordost',
'Osten', 'Ost-Südost', 'Südost', 'Süd-Südost',
'Süden', 'Süd-Südwest', 'Südwest', 'West-Südwest',
'Westen', 'West-Nordwest', 'Nordwest', 'Nord-Nordwest'
],
en => [
'North', 'North-Northeast',
'Northeast', 'East-Northeast',
'East', 'East-Southeast',
'Southeast', 'South-Southeast',
'South', 'South-Southwest',
'Southwest', 'West-Southwest',
'West', 'West-Northwest',
'Northwest', 'North-Northwest'
],
},
scope => {
de => [
{
ge => 0.0,
lt => 22.5,
eq => 360,
regex => '^(N|Norden|0)$',
value_num => 0.0,
},
{
ge => 22.5,
lt => 45.0,
regex => '^(NNO|Nord-Nordost|1)$',
value_num => 22.5,
},
{
ge => 45.0,
lt => 67.5,
regex => '^(NO|Nordost|2)$',
value_num => 45.0,
},
{
ge => 67.5,
lt => 90.0,
regex => '^(ONO|Ost-Nordost|3)$',
value_num => 67.5,
},
{
ge => 90.0,
lt => 112.5,
regex => '^(O|Osten|4)$',
value_num => 90.0,
},
{
ge => 112.5,
lt => 135.0,
regex => '^(OSO|Ost-S(ü|ue)dost|5)$',
value_num => 112.5,
},
{
ge => 135.0,
lt => 157.5,
regex => '^(SO|S(ü|ue)dost|6)$',
value_num => 135.0,
},
{
ge => 157.5,
lt => 180.0,
regex => '^(SSO|S(ü|ue)d-S(ü|ue)dost|7)$',
value_num => 157.5,
},
{
ge => 180.0,
lt => 202.5,
regex => '^(S|S(ü|ue)den|8)$',
value_num => 180.0,
},
{
ge => 202.5,
lt => 225.0,
regex => '^(SSW|S(ü|ue)d-S(ü|ue)dwest|9)$',
value_num => 202.5,
},
{
ge => 225.0,
lt => 247.5,
regex => '^(SW|S(ü|ue)dwest|10)$',
value_num => 225.0,
},
{
ge => 247.5,
lt => 270.0,
regex => '^(WSW|West-S(ü|ue)dwest|11)$',
value_num => 247.5,
},
{
ge => 270.0,
lt => 292.5,
regex => '^(W|Westen|12)$',
value_num => 270.0,
},
{
ge => 292.5,
lt => 315.0,
regex => '^(WNW|West-Nordwest|13)$',
value_num => 292.5,
},
{
ge => 315.0,
lt => 337.5,
regex => '^(NW|Nordwest|14)$',
value_num => 315.0,
},
{
ge => 337.5,
lt => 360,
regex => '^(NNW|Nord-Nordwest|15)$',
value_num => 337.5,
},
],
en => [
{
ge => 0.0,
lt => 22.5,
eq => 360,
regex => '^(N|North|0',
value_num => 0.0,
},
{
ge => 22.5,
lt => 45.0,
regex => '^(NNE|North-Northeast|1)$',
value_num => 22.5,
},
{
ge => 45.0,
lt => 67.5,
regex => '^(NE|Northeast|2)$',
value_num => 45.0,
},
{
ge => 67.5,
lt => 90.0,
regex => '^(ENE|East-Northeast|3)$',
value_num => 67.5,
},
{
ge => 90.0,
lt => 112.5,
regex => '^(E|East|4)$',
value_num => 90.0,
},
{
ge => 112.5,
lt => 135.0,
regex => '^(ESE|East-Southeast|5)$',
value_num => 112.5,
},
{
ge => 135.0,
lt => 157.5,
regex => '^(SE|Southeast|6)$',
value_num => 135.0,
},
{
ge => 157.5,
lt => 180.0,
regex => '^(SSE|South-Southeast|7)$',
value_num => 157.5,
},
{
ge => 180.0,
lt => 202.5,
regex => '^(S|South|8)$',
value_num => 180.0,
},
{
ge => 202.5,
lt => 225.0,
regex => '^(SSW|South-Southwest|9)$',
value_num => 202.5,
},
{
ge => 225.0,
lt => 247.5,
regex => '^(SW|Southwest|10)$',
value_num => 225.0,
},
{
ge => 247.5,
lt => 270.0,
regex => '^(WSW|West-Southwest|11)$',
value_num => 247.5,
},
{
ge => 270.0,
lt => 292.5,
regex => '^(W|West|12)$',
value_num => 270.0,
},
{
ge => 292.5,
lt => 315.0,
regex => '^(WNW|West-Northwest|13)$',
value_num => 292.5,
},
{
ge => 315.0,
lt => 337.5,
regex => '^(NW|Northwest|14)$',
value_num => 315.0,
},
{
ge => 337.5,
lt => 360,
regex => '^(NNW|North-Northwest|15)$',
value_num => 337.5,
},
],
},
rtype_description => {
de => 'Himmelsrichtung',
en => 'point of the compass',
},
tmpl_long => '%txt_long%',
},
closure => {
ref_base => 900,
txt => {
de => [ 'geschlossen', 'offen', 'gekippt' ],
en => [ 'closed', 'open', 'tilted' ],
},
scope => [ '^(closed|0)$', '^(open|1)$', '^(tilted|2)$' ],
rtype_description => {
de => 'Status für Fenster und Türen',
en => 'state for windows and doors',
},
},
condition_weather => {
ref_base => 900,
txt => {
de => [ 'klar', 'sonnig ', 'bewölkt', 'Regen' ],
en => [ 'clear', 'sunny', 'cloudy', 'rain' ],
},
scope => [ '^(clear|0)$', '^(sunny|1)$', '^(cloudy|2)$', '^(rain|3)$' ],
rtype_description => {
de => 'Wetterbedingung',
en => 'weather condition',
},
},
condition_hum => {
ref_base => 900,
txt => {
de => [ 'trocken', 'niedrig', 'optimal', 'hoch', 'feucht' ],
en => [ 'dry', 'low', 'optimal', 'high', 'wet' ],
},
scope => [
'^(dry|0)$', '^(low|1)$', '^(optimal|2)$', '^(high|3)$',
'^(wet|4)$'
],
rtype_description => {
de => 'Feuchtigkeitsbedingung',
en => 'humidity condition',
},
},
condition_uvi => {
ref_base => 900,
txt => {
de => [ 'niedrig', 'moderat', 'hoch', 'sehr hoch', 'extrem' ],
en => [ 'low', 'moderate', 'high', 'very high', 'extreme' ],
},
scope => [
'^(low|0)$', '^(moderate|1)$',
'^(high|2)$', '^(veryhigh|3)$',
'^(extreme|4)$'
],
rtype_description => {
de => 'UV Bedingung',
en => 'UV condition',
},
},
# logical operators
bool => {
ref_base => 26,
txt => {
de => [ 'falsch', 'wahr' ],
en => [ 'false', 'true' ],
},
scope => [ '^(false|no|0)$', '^(true|yes|1)$' ],
rtype_description => {
de => 'Boolesch wahr/falsch',
en => 'Boolean true/false',
},
},
yesno => {
ref_base => 26,
txt => {
de => [ 'nein', 'ja' ],
en => [ 'no', 'yes' ],
},
scope => [ '^(no|n|false|0)$', '^(yes|y|true|1)$' ],
rtype_description => {
de => 'Boolesch ja/nein',
en => 'Boolean ja/nein',
},
},
# numbering
short => {
ref_base => 25,
format => '%i',
rtype_description => {
de => 'Ganzzahl zwischen -32768 und 32767',
en => 'Integer between -32768 and 32767',
},
scope => { minValue => -32768, maxValue => 32767 },
},
rshort => {
ref_base => 25,
format => '%.0f',
rtype_description => {
de => 'gerundete Ganzzahl zwischen -32768 und 32767',
en => 'rounded integer between -32768 and 32767',
},
scope => { minValue => -32768, maxValue => 32767 },
},
long => {
ref_base => 25,
format => '%i',
rtype_description => {
de => 'Ganzzahl zwischen -2147483648 und 214748364',
en => 'Integer between -2147483648 and 214748364',
},
scope => { minValue => -2147483648, maxValue => 214748364 },
},
rlong => {
ref_base => 25,
format => '%.0f',
rtype_description => {
de => 'gerundete Ganzzahl zwischen -2147483648 und 214748364',
en => 'rounded integer between -2147483648 and 214748364',
},
scope => { minValue => -2147483648, maxValue => 214748364 },
},
integer => {
ref_base => 25,
format => '%i',
rtype_description => {
de => 'Ganzzahl',
en => 'Integer',
},
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
rinteger => {
ref_base => 25,
format => '%.0f',
rtype_description => {
de => 'gerundete Ganzzahl',
en => 'rounded integer',
},
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
float => {
ref_base => 25,
format => '%.2f',
rtype_description => {
de => 'Fließkommazahl',
en => 'floating number',
},
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
pct => {
ref_base => 25,
format => '%i',
symbol => chr(0x0025),
suffix => 'pct',
txt => {
de => 'Prozent',
en => 'percent',
},
scope => { minValue => 0, maxValue => 100 },
},
# currency
#https://en.wikipedia.org/wiki/Euro
#https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro
euro => {
ref_base => 24,
format => '%.2f',
symbol => '€',
suffix => 'EUR',
txt => {
de => 'Euro',
en => 'euro',
},
tmpl => {
de => '%symbol%%value%',
de_de => '%value%' . chr(0x202F) . '%symbol%',
en => '%symbol%%value%',
},
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
pound_uk => {
ref_base => 24,
format => '%.2f',
symbol => '£',
suffix => 'GBP',
txt => {
de => 'Pfund',
en => 'pound',
},
txt_long => {
de => 'Britisches Pfund',
en => 'British pound',
},
txt_long_pl => {
de => 'Britische Pfund',
en => 'British pound',
},
tmpl => '%symbol%%value%',
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
dollar_us => {
ref_base => 24,
format => '%.2f',
symbol => '$',
suffix => 'USD',
txt => {
de => 'Dollar',
en => 'dollar',
},
txt_long => {
de => 'US Dollar',
en => 'US dollar',
},
tmpl => '%symbol%%value%',
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
cent => {
ref_base => 24,
format => '%.0f',
symbol => {
de => 'ct',
en => '¢',
},
suffix => {
de => 'ct',
en => 'c',
},
txt => {
de => 'Cent',
en => 'cent',
},
tmpl => {
de => '%value%%symbol%',
de_de => '%value%' . chr(0x202F) . '%symbol%',
en => '%value%%symbol%',
},
scope => '^([0-9]*(?:\.[0-9]*)?)$',
},
# plane angular
gon => {
ref_base => 14,
symbol => chr(0x00B0),
suffix => 'gon',
txt => {
de => 'Grad',
en => 'gradians',
},
tmpl => '%value%%symbol%',
scope => { minValue => 0 },
},
rad => {
ref_base => 14,
suffix => 'rad',
txt => {
de => 'Radiant',
en => 'radiant',
},
scope => { minValue => 0 },
},
# temperature
c => {
ref_base => 4,
symbol => chr(0x00B0) . 'C',
suffix => 'C',
txt => {
de => 'Grad Celsius',
en => 'Degree Celsius',
},
txt_pl => {
de => 'Grad Celsius',
en => 'Degrees Celsius',
},
scope => { minValue => -273.15 },
},
f => {
ref_base => 4,
symbol => chr(0x00B0) . 'F',
suffix => 'F',
txt => {
de => 'Grad Fahrenheit',
en => 'Degree Fahrenheit',
},
txt_pl => {
de => 'Grad Fahrenheit',
en => 'Degrees Fahrenheit',
},
scope => { minValue => -459.67 },
},
k => {
ref_base => 4,
suffix => 'K',
txt => {
de => 'Kelvin',
en => 'Kelvin',
},
},
# pressure
bar => {
ref_base => 10,
scale_m => '1.0e0',
suffix => 'bar',
txt => {
de => 'Bar',
en => 'Bar',
},
},
mbar => {
ref => 'bar',
scale_m => '1.0e-3',
},
pa => {
ref_base => 10,
scale_m => '1.0e0',
suffix => 'Pa',
txt => {
de => 'Pascal',
en => 'Pascal',
},
},
hpa => {
ref => 'pa',
scale_m => '1.0e2',
},
pamb => {
ref_base => 12,
scale_m => '1.0e0',
suffix => 'Pa',
txt => {
de => 'Pascal',
en => 'Pascal',
},
},
hpamb => {
ref => 'pamb',
scale_m => '1.0e2',
},
inhg => {
ref_base => 12,
suffix => 'inHg',
txt => {
de => 'Zoll Quecksilbersäule',
en => 'Inches of Mercury',
},
},
mmhg => {
ref_base => 12,
suffix => 'mmHg',
txt => {
de => 'Millimeter Quecksilbersäule',
en => 'Milimeter of Mercury',
},
},
# length
km => {
ref => 'm',
scale_m => '1.0e3',
},
hm => {
ref => 'm',
scale_m => '1.0e2',
},
dam => {
ref => 'm',
scale_m => '1.0e1',
},
m => {
ref_base => 0,
scale_m => '1.0e0',
suffix => 'm',
txt => {
de => 'Meter',
en => 'meter',
},
},
dm => {
ref => 'm',
scale_m => '1.0e-1',
},
cm => {
ref => 'm',
scale_m => '1.0e-2',
},
mm => {
ref => 'm',
scale_m => '1.0e-3',
},
um => {
ref => 'm',
scale_m => '1.0e-6',
},
nm => {
ref => 'm',
scale_m => '1.0e-9',
},
pm => {
ref => 'm',
scale_m => '1.0e-12',
},
fm => {
ref => 'm',
scale_m => '1.0e-15',
},
in => {
ref_base => 0,
symbol => '″',
suffix => 'in',
txt => {
de => 'Zoll',
en => 'inch',
},
txt_pl => {
de => 'Zoll',
en => 'inches',
},
tmpl => '%value%%symbol%',
},
ft => {
ref_base => 0,
symbol => '',
suffix => 'ft',
txt => {
de => 'Fuss',
en => 'foot',
},
txt_pl => {
de => 'Fuss',
en => 'feet',
},
tmpl => '%value%%symbol%',
},
yd => {
ref_base => 0,
suffix => 'yd',
txt => {
de => 'Yard',
en => 'yard',
},
txt_pl => {
de => 'Yards',
en => 'yards',
},
},
mi => {
ref_base => 0,
suffix => 'mi',
txt => {
de => 'Meilen',
en => 'miles',
},
},
# time
sec => {
ref_base => 2,
scale_t => '1',
suffix => {
de => 's',
en => 's',
},
txt => {
de => 'Sekunde',
en => 'second',
},
txt_pl => {
de => 'Sekunden',
en => 'seconds',
},
},
min => {
ref_base => 2,
scale_t => '60',
suffix => {
de => 'Min',
en => 'min',
},
txt => {
de => 'Minute',
en => 'minute',
},
txt_pl => {
de => 'Minuten',
en => 'minutes',
},
},
hr => {
ref_base => 2,
scale_t => '3600',
suffix => 'h',
txt => {
de => 'Stunde',
en => 'hour',
},
txt_pl => {
de => 'Stunden',
en => 'hours',
},
},
d => {
ref_base => 2,
scale_t => '86400',
suffix => {
de => 'T',
en => 'd',
},
txt => {
de => 'Tag',
en => 'day',
},
txt_pl => {
de => 'Tage',
en => 'days',
},
},
w => {
ref_base => 2,
scale_t => '604800',
suffix => {
de => 'W',
en => 'w',
},
txt => {
de => 'Woche',
en => 'week',
},
txt_pl => {
de => 'Wochen',
en => 'weeks',
},
},
mon => {
ref_base => 2,
scale_t => '2592000',
suffix => {
de => 'M',
en => 'm',
},
txt => {
de => 'Monat',
en => 'month',
},
txt_pl => {
de => 'Monate',
en => 'Monat',
},
},
y => {
ref_base => 2,
scale_t => '31536000',
suffix => {
de => 'J',
en => 'y',
},
txt => {
de => 'Jahr',
en => 'year',
},
txt_pl => {
de => 'Jahre',
en => 'years',
},
},
epoch => {
ref_base => 2,
scale_t => '1',
scope => { minValue => 0 },
rtype_description => {
de => 'Unix Epoche in s seit 1970-01-01T00:00:00Z',
en => 'Unix epoch in s since 1970-01-01T00:00:00Z',
},
},
time => {
ref_base => 2,
scope => '^(([0-1]?[0-9]|[0-2]?[0-3]):([0-5]?[0-9]))$',
rtype_description => {
de => 'Uhrzeit hh:mm',
en => 'time hh:mm',
},
tmpl_long => {
de => '%value%' . chr(0x00A0) . 'Uhr',
en => '%value%',
}
},
datetime => {
ref_base => 2,
scope =>
'^(([1-2][0-9]{3})-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|30|31) (0?[1-9]|1[0-9]|2[0-3]):(0?[1-9]|[1-5][0-9]))$',
rtype_description => {
de => 'Datum+Uhrzeit YYYY-mm-dd hh:mm',
en => 'date+time YYYY-mm-dd hh:mm',
},
},
timesec => {
ref_base => 900,
scope => '^(([0-1]?[0-9]|[0-2]?[0-3]):([0-5]?[0-9]):([0-5]?[0-9]))$',
rtype_description => {
de => 'Uhrzeit hh:mm:ss',
en => 'time hh:mm:ss',
},
},
datetimesec => {
ref_base => 2,
scope =>
'^(([1-2][0-9]{3})-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|30|31) (0?[1-9]|1[0-9]|2[0-3]):(0?[1-9]|[1-5][0-9]):(0?[1-9]|[1-5][0-9]))$',
rtype_description => {
de => 'Datum+Uhrzeit YYYY-mm-dd hh:mm:ss',
en => 'date+time YYYY-mm-dd hh:mm:ss',
},
},
# speed
bft => {
ref_base => 15,
suffix => 'bft',
txt => {
de => 'Windstärke',
en => 'wind force',
},
tmpl_long => '%txt% %value%',
},
kn => {
ref_base => 15,
suffix => 'kn',
txt => {
de => 'Knoten',
en => 'knots',
},
},
fts => {
ref_base => 15,
ref => 'ft',
ref_t => 'sec',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_t%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_t%',
},
},
mph => {
ref_base => 15,
ref => 'mi',
ref_t => 'hr',
tmpl => '%value%' . chr(0x00A0) . 'mph',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_t%',
},
},
kmh => {
ref_base => 15,
ref => 'm',
ref_t => 'hr',
scale_m => '1.0e3',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_t%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_t%',
},
},
mps => {
ref_base => 15,
ref => 'm',
ref_t => 'sec',
scale_m => '1.0e0',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_t%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_t%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_t%',
},
},
# weight
mol => {
ref_base => 5,
suffix => 'mol',
},
pg => {
ref => 'g',
scale_m => "1.0e-12",
},
ng => {
ref => 'g',
scale_m => "1.0e-9",
},
ug => {
ref => 'g',
scale_m => "1.0e-6",
},
mg => {
ref => 'g',
scale_m => "1.0e-3",
},
cg => {
ref => 'g',
scale_m => "1.0e-2",
},
dg => {
ref => 'g',
scale_m => "1.0e-1",
},
g => {
ref_base => 1,
scale_m => "1.0e0",
suffix => 'g',
txt => {
de => 'Gramm',
en => 'gram',
},
},
kg => {
ref => 'g',
scale_m => "1.0e3",
},
t => {
ref => 'g',
scale_m => "1.0e6",
suffix => 't',
txt => {
de => 'Tonne',
en => 'ton',
},
txt_pl => {
de => 'Tonnen',
en => 'tons',
},
},
lb => {
ref_base => 1,
suffix => 'lb',
txt => {
de => 'Pfund',
en => 'pound',
},
},
lbs => {
ref_base => 1,
suffix => 'lbs',
txt => {
de => 'Pfund',
en => 'pound',
},
},
# luminous intensity
cd => {
ref_base => 6,
suffix => 'cd',
txt => {
de => 'Candela',
en => 'Candela',
},
},
# illumination intensity
lx => {
ref_base => 16,
suffix => 'lx',
txt => {
de => 'Lux',
en => 'Lux',
},
},
# luminous flux
lm => {
ref_base => 17,
suffix => 'lm',
txt => {
de => 'Lumen',
en => 'Lumen',
},
},
uvi => {
ref_base => 900,
suffix => 'UVI',
txt => {
de => 'UV-Index',
en => 'UV-Index',
},
tmpl => '%suffix%' . chr(0x00A0) . '%value%',
tmpl_long => '%txt%' . chr(0x00A0) . '%value%',
format => '%i',
},
# surface area
cm2 => {
ref_base => 23,
ref => 'm',
scale_m => '1.0e-2',
scale_sq => 1,
},
m2 => {
ref_base => 23,
ref => 'm',
scale_m => '1.0e0',
scale_sq => 1,
},
# volume
cm3 => {
ref_base => 18,
ref => 'm',
scale_m => '1.0e-2',
scale_cu => 1,
},
m3 => {
ref_base => 18,
ref => 'm',
scale_m => '1.0e0',
scale_cu => 1,
},
ml => {
ref => 'l',
scale_m => '1.0e-3',
},
l => {
ref_base => 18,
suffix => 'l',
txt => {
de => 'Liter',
en => 'liter',
},
txt_pl => {
de => 'Liter',
en => 'liters',
},
},
hl => {
ref => 'l',
scale_m => '1.0e2',
},
b => {
ref_base => 19,
scale_m => '1.0e0',
suffix => 'B',
txt => {
de => 'Bel',
en => 'Bel',
},
},
db => {
ref => 'b',
scale_m => '1.0e-1',
},
ua => {
ref => 'a',
scale_m => '1.0e-6',
},
ma => {
ref => 'a',
scale_m => '1.0e-3',
},
a => {
ref_base => 3,
scale_m => '1.0e0',
suffix => 'A',
txt => {
de => 'Ampere',
en => 'Ampere',
},
},
uv => {
ref => 'v',
scale_m => '1.0e-6',
},
mv => {
ref => 'v',
scale_m => '1.0e-3',
},
v => {
ref_base => 13,
scale_m => '1.0e0',
suffix => 'V',
txt => {
de => 'Volt',
en => 'Volt',
},
},
uj => {
ref => 'j',
scale_m => '1.0e-6',
},
mj => {
ref => 'j',
scale_m => '1.0e-3',
},
j => {
ref_base => 7,
scale_m => '1.0e0',
suffix => 'J',
txt => {
de => 'Joule',
en => 'Joule',
},
},
uw => {
ref => 'w',
scale_m => '1.0e-6',
},
mw => {
ref => 'w',
scale_m => '1.0e-3',
},
w => {
ref_base => 9,
scale_m => '1.0e0',
suffix => 'Watt',
txt => {
de => 'Watt',
en => 'Watt',
},
},
va => {
ref => 'w',
},
uwpscm => {
ref => 'w',
scale_m => '1.0e-6',
ref_sq => 'm',
scale_sq => '1.0e-2',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
uwpsm => {
ref => 'w',
scale_m => '1.0e-6',
ref_sq => 'm',
scale_sq => '1.0e0',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
mwpscm => {
ref => 'w',
scale_m => '1.0e-3',
ref_sq => 'm',
scale_sq => '1.0e-2',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
mwpsm => {
ref => 'w',
scale_m => '1.0e-3',
ref_sq => 'm',
scale_sq => '1.0e0',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
wpscm => {
ref => 'w',
scale_m => '1.0e0',
ref_sq => 'm',
scale_sq => '1.0e-2',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
wpsm => {
ref => 'w',
scale_m => '1.0e0',
ref_sq => 'm',
scale_sq => '1.0e0',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%/%suffix_sq%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt% per %txt_sq%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl% pro %txt_sq%',
en => '%value%' . chr(0x00A0) . '%txt_pl% per %txt_sq%',
},
},
coul => {
ref_base => 20,
suffix => 'C',
txt => {
de => 'Coulomb',
en => 'Coulomb',
},
},
far => {
ref_base => 21,
suffix => 'F',
txt => {
de => 'Farad',
en => 'Farad',
},
},
ohm => {
ref_base => 22,
symbol => 'Ω',
suffix => 'Ohm',
txt => {
de => 'Ohm',
en => 'Ohm',
},
},
};
# helps the user to assign rtypes to existing readings of modules
# w/o built-in rtype support
#
# layer 1 = module name (exception: global is valid for all modules but
# with lower preference)
# layer 2 = reading name as used by that module
# layer 3 = template for this reading to be copied to device attribute readingsDesc.
# aliasname makes a reference to another reading name to avoid duplicates
# and take the opportunity to harmonise reading names here.
my $readingsDB = {
global => {
airpress => {
aliasname => 'pressure_hpa', # alias only
},
azimuth => {
rtype => 'gon',
},
compasspoint => {
rtype => 'compasspoint'
},
daylight => {
rtype => 'yesno',
},
dewpoint => {
aliasname => 'dewpoint_c', # alias only
},
dewpoint_c => {
rtype => 'c',
},
elevation => {
rtype => 'gon',
},
feelslike => {
aliasname => 'feelslike_c', # alias only
},
feelslike_c => {
rtype => 'c',
},
humidity => {
rtype => 'pct',
formula_symbol => 'H',
},
humidityabs => {
aliasname => 'humidityabs_c', # alias only
},
humidityabs_c => {
rtype => 'c',
formula_symbol => 'H',
},
humidityabs_f => {
rtype => 'f',
formula_symbol => 'H',
},
humidityabs_k => {
rtype => 'k',
formula_symbol => 'H',
},
horizon => {
rtype => 'gon',
},
indoordewpoint => {
aliasname => 'indoordewpoint_c', # alias only
},
indoordewpoint_c => {
rtype => 'c',
},
indoorhumidity => {
rtype => 'pct',
formula_symbol => 'H',
},
indoorhumidityabs => {
aliasname => 'indoorhumidityabs_c', # alias only
},
indoorhumidityabs_c => {
rtype => 'c',
},
indoortemperature => {
aliasname => 'indoortemperature_c', # alias only
},
indoortemperature_c => {
rtype => 'c',
},
israining => {
rtype => 'yesno',
},
level => {
rtype => 'pct',
},
luminosity => {
rtype => 'lx',
},
pct => {
rtype => 'pct',
},
pressure => {
aliasname => 'pressure_hpa', # alias only
},
pressure_hpa => {
rtype => 'hpamb',
},
pressure_in => {
rtype => 'inhg',
},
pressure_mm => {
rtype => 'mmhg',
},
pressureabs => {
aliasname => 'pressureabs_hpamb', # alias only
},
pressureabs_hpamb => {
rtype => 'hpamb',
},
pressureabs_in => {
rtype => 'inhg',
},
pressureabs_mm => {
rtype => 'mmhg',
},
pressureabs_psi => {
aliasname => 'pressureabs_psia',
},
pressureabs_psia => {
rtype => 'psia',
},
rain => {
aliasname => 'rain_mm', # alias only
},
rain_mm => {
rtype => 'mm',
},
rain_day => {
aliasname => 'rain_day_mm', # alias only
},
rain_day_mm => {
rtype => 'mm',
},
rain_night => {
aliasname => 'rain_night_mm', # alias only
},
rain_night_mm => {
rtype => 'mm',
},
rain_week => {
aliasname => 'rain_week_mm', # alias only
},
rain_week_mm => {
rtype => 'mm',
},
rain_month => {
aliasname => 'rain_month_mm', # alias only
},
rain_month_mm => {
rtype => 'mm',
},
rain_year => {
aliasname => 'rain_year_mm', # alias only
},
rain_year_mm => {
rtype => 'mm',
},
snow => {
aliasname => 'snow_cm', # alias only
},
snow_cm => {
rtype => 'cm',
},
snow_day => {
aliasname => 'snow_day_cm', # alias only
},
snow_day_cm => {
rtype => 'cm',
},
snow_night => {
aliasname => 'snow_night_cm', # alias only
},
snow_night_cm => {
rtype => 'cm',
},
sunshine => {
aliasname => 'solarradiation', # alias only
},
solarradiation => {
rtype => 'wpsm',
},
temp => {
aliasname => 'temperature_c', # alias only
},
temp_c => {
aliasname => 'temperature_c', # alias only
},
temperature => {
aliasname => 'temperature_c', # alias only
},
temperature_c => {
rtype => 'c',
},
uv => {
aliasname => 'uvi', # alias only
},
uvi => {
rtype => 'uvi',
},
uvr => {
rtype => 'uwpscm',
},
valvedesired => {
aliasname => 'valve', # alias only
},
valvepos => {
aliasname => 'valve', # alias only
},
valveposition => {
aliasname => 'valve', # alias only
},
valvepostc => {
aliasname => 'valve', # alias only
},
valve => {
rtype => 'pct',
},
visibility => {
aliasname => 'visibility_km', # alias only
},
visibility_km => {
rtype => 'km',
},
wind_chill => {
aliasname => 'wind_chill_c', # alias only
},
wind_chill_c => {
rtype => 'c',
},
wind_compasspoint => {
rtype => 'compasspoint'
},
windspeeddirection => {
aliasname => 'wind_compasspoint', # alias only
},
winddirectiontext => {
aliasname => 'wind_compasspoint', # alias only
},
wind_direction => {
aliasname => 'wind_compasspoint', # alias only
},
wind_dir => {
aliasname => 'wind_compasspoint', # alias only
},
winddir => {
aliasname => 'wind_compasspoint', # alias only
},
winddirection => {
aliasname => 'wind_compasspoint', # alias only
},
wind_gust => {
aliasname => 'wind_gust_kmh', # alias only
},
wind_gust_kmh => {
rtype => 'kmh',
},
wind_speed => {
aliasname => 'wind_speed_kmh', # alias only
},
wind_speed_kmh => {
rtype => 'kmh',
},
}
};
# Find rtype through reading name
sub rname2rtype ($$@) {
my ( $name, $reading, $lang ) = @_;
my $details;
my $r = $reading;
my $l = ( $lang ? lc($lang) : "en" );
my $rt;
my $guess;
my %return;
# remove some prefix or other values to
# flatten reading name
$r =~ s/^fc\d+_//i;
$r =~ s/_(min|max|avg|sum|cum|avg\d+m|sum\d+m|cum\d+m)_/_/i;
$r =~ s/^(min|max|avg|sum|cum|avg\d+m|sum\d+m|cum\d+m)_//i;
$r =~ s/_(min|max|avg|sum|cum|avg\d+m|sum\d+m|cum\d+m)$//i;
$r =~ s/.*[-_](temp)$/$1/i;
# rename capital letter containing readings
if ( !$readingsDB->{global}{ lc($r) } ) {
$r =~ s/^([A-Z])(.*)/\l$1$2/;
$r =~ s/([A-Z][a-z0-9]+)[\/\|\-_]?/_$1/g;
}
$r = lc($r);
# known aliasname reading names
if ( $readingsDB->{global}{$r}{aliasname} ) {
my $dr = $readingsDB->{global}{$r}{aliasname};
$return{aliasname} = $dr;
$return{shortname} = $readingsDB->{global}{$dr}{shortname};
$rt = (
$readingsDB->{global}{$dr}{rtype}
? $readingsDB->{global}{$dr}{rtype}
: "-"
);
}
# known standard reading names
elsif ( $readingsDB->{global}{$r}{shortname} ) {
$return{aliasname} = $reading;
$return{shortname} = $readingsDB->{global}{$r}{shortname};
$rt = (
$readingsDB->{global}{$r}{rtype}
? $readingsDB->{global}{$r}{rtype}
: "-"
);
}
# just guessing the rtype from reading name format
elsif ( $r =~ /^.*_([A-Za-z0-9]+)$/i ) {
$return{guess} = 1;
$rt = $1;
}
return $rt if ( $rt && $rtypes->{$rt} );
}
######################################
# package main
#
package main;
use utf8;
# Do the magic to generate value + unit combined text strings
# for specified language
sub replaceTemplate ($$$$;$) {
my ( $device, $reading, $odesc, $lang, $value ) = @_;
my $l = ( $lang ? lc($lang) : "en" );
my $txt;
my $txt_long;
my $r = $defs{$device}{READINGS} if ($device);
return
if ( !$odesc || ref($odesc) ne "HASH" );
$value = ${$odesc}{value}{$lang}
if (!defined($value)
&& defined( $odesc->{value} )
&& defined( $odesc->{value}{$lang} ) );
return $value
if ( !defined($value) || $value eq "" );
# clone
my $desc;
foreach ( keys %{$odesc} ) {
$desc->{$_} = $odesc->{$_};
}
##########
# language support
#
# keep only defined language if set
foreach ( keys %{$desc} ) {
next if ( !defined( $desc->{$_} )
|| ref( $desc->{$_} ) ne "HASH" );
# find any direct format
if ( defined( $desc->{$_}{$l} ) ) {
my $v;
$v = $desc->{$_}{$l};
delete $desc->{$_};
$desc->{$_} = $v if ( defined($v) );
}
# try base language format instead
# and extract xx from xx_yz
elsif ( $l =~ /^([a-z]+)(_([a-z]+))?$/i
&& defined( $desc->{$_}{$1} ) )
{
my $v;
$v = $desc->{$_}{$1};
delete $desc->{$_};
$desc->{$_} = $v if ( defined($v) );
}
}
# if original value was a text string and we got value_num out of it,
# now try to find the right standardized text value from the ARRAY
if ( defined( $desc->{value_num} ) ) {
# suffix
if ( ref( $desc->{suffix} ) eq "ARRAY"
&& $desc->{suffix}[ $desc->{value_num} ] )
{
my $v = $desc->{suffix}[ $desc->{value_num} ];
delete $desc->{suffix};
$desc->{suffix} = $v;
}
# symbol
if ( ref( $desc->{symbol} ) eq "ARRAY"
&& $desc->{symbol}[ $desc->{value_num} ] )
{
my $v = $desc->{symbol}[ $desc->{value_num} ];
delete $desc->{symbol};
$desc->{symbol} = $v;
}
# txt
if ( ref( $desc->{txt} ) eq "ARRAY"
&& $desc->{txt}[ $desc->{value_num} ] )
{
my $v = $desc->{txt}[ $desc->{value_num} ];
delete $desc->{txt};
$desc->{txt} = $v;
}
# txt_pl
if ( ref( $desc->{txt_pl} ) eq "ARRAY"
&& $desc->{txt_pl}[ $desc->{value_num} ] )
{
my $v = $desc->{txt_pl}[ $desc->{value_num} ];
delete $desc->{txt_pl};
$desc->{txt_pl} = $v;
}
# txt_long
if ( ref( $desc->{txt_long} ) eq "ARRAY"
&& $desc->{txt_long}[ $desc->{value_num} ] )
{
my $v = $desc->{txt_long}[ $desc->{value_num} ];
delete $desc->{txt_long};
$desc->{txt_long} = $v;
}
# txt_long_pl
if ( ref( $desc->{txt_long_pl} ) eq "ARRAY"
&& $desc->{txt_long_pl}[ $desc->{value_num} ] )
{
my $v = $desc->{txt_long_pl}[ $desc->{value_num} ];
delete $desc->{txt_long_pl};
$desc->{txt_long_pl} = $v;
}
}
##########
# template support
#
# add metric name to suffix
$desc->{suffix} = $desc->{scale_txt_m} . $desc->{suffix}
if ( $desc->{suffix}
&& $desc->{scale_txt_m} );
$desc->{txt} = $desc->{scale_txt_long_m} . lc( $desc->{txt} )
if ( $desc->{txt}
&& $desc->{scale_txt_long_m} );
# add square information to suffix and txt
# if no separate suffix_sq and txt_sq was found
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_sq}
if (!$desc->{suffix_sq}
&& $desc->{scale_txt_sq} );
$desc->{txt} = $desc->{scale_txt_long_sq} . lc( $desc->{txt} )
if (!$desc->{txt_sq}
&& $desc->{scale_txt_long_sq} );
# add cubic information to suffix and txt
# if no separate suffix_cu and txt_cu was found
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_cu}
if (!$desc->{suffix_cu}
&& $desc->{scale_txt_cu} );
$desc->{txt} = $desc->{scale_txt_long_cu} . lc( $desc->{txt} )
if (!$desc->{txt_cu}
&& $desc->{scale_txt_long_cu} );
# add metric name to suffix_sq
$desc->{suffix_sq} = $desc->{scale_txt_m_sq} . $desc->{suffix_sq}
if ( $desc->{suffix_sq}
&& $desc->{scale_txt_m_sq}
&& $desc->{suffix_sq} !~ /$desc->{scale_txt_m_sq}/ );
$desc->{txt_sq} = $desc->{scale_txt_long_m_sq} . lc( $desc->{txt_sq} )
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_m_sq} );
# # add square information to suffix_sq
$desc->{suffix_sq} = $desc->{suffix_sq} . $desc->{scale_txt_sq}
if ( $desc->{suffix_sq}
&& $desc->{scale_txt_sq} );
$desc->{txt_sq} = $desc->{scale_txt_long_sq} . lc( $desc->{txt_sq} )
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_sq} );
# add metric name to suffix_cu
$desc->{suffix_cu} = $desc->{scale_txt_m_cu} . $desc->{suffix_cu}
if ( $desc->{suffix_cu}
&& $desc->{scale_txt_m_cu} );
$desc->{txt_cu} = $desc->{scale_txt_long_m_cu} . lc( $desc->{txt_cu} )
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_m_cu} );
# add cubic information to suffix_cu
$desc->{suffix_cu} = $desc->{suffix_cu} . $desc->{scale_txt_cu}
if ( $desc->{suffix_cu}
&& $desc->{scale_txt_cu} );
$desc->{txt_cu} = $desc->{scale_txt_long_cu} . lc( $desc->{txt_cu} )
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_cu} );
###############################
# generate short text string
#
# find text template
$txt = '%value%' . chr(0x00A0) . '%suffix%' if ( !$desc->{symbol} );
$txt = '%value%' . chr(0x202F) . '%symbol%' if ( $desc->{symbol} );
$txt = $desc->{tmpl} if ( $desc->{tmpl} );
# Replace all %text% placeholders with text value found
# in old style READINGS.
# Normally only TIME & VAL would be available but some modules might have
# extended this and we want people to be able to make use of it.
if ( $r && $reading && $r->{$reading} ) {
foreach my $k ( keys %{ $r->{$reading} } ) {
$txt =~ s/%$k%/$r->{$reading}{$k}/g;
}
}
# Replace all %text% placeholders with text value found
# in matching reading desc hash keys
foreach my $k ( keys %{$desc} ) {
my $vdm = $desc->{$k};
$vdm = UConv::decimal_mark( $vdm, $desc->{decimal_mark} )
if ( defined( $desc->{decimal_mark} ) );
$txt =~ s/%$k%/$vdm/g;
}
return ($txt) if ( !wantarray );
###############################
# generate long text string in plural
#
# find text template
if ( looks_like_number($value)
&& ( $value eq "0" || $value > 1 )
&& $desc->{txt_long_pl} )
{
$txt_long = '%value%' . chr(0x00A0) . '%txt_long_pl%';
$txt_long = $desc->{tmpl_long_pl}
if ( $desc->{tmpl_long_pl} );
}
elsif (looks_like_number($value)
&& ( $value eq "0" || $value > 1 )
&& $desc->{txt_pl} )
{
$txt_long = '%value%' . chr(0x00A0) . '%txt_pl%';
$txt_long = $desc->{tmpl_long_pl}
if ( $desc->{tmpl_long_pl} );
}
###############################
# generate long text string in singular
#
# find text template
elsif ( $desc->{txt_long} ) {
$txt_long = '%value%' . chr(0x00A0) . '%txt_long%';
$txt_long = $desc->{tmpl_long}
if ( $desc->{tmpl_long} );
}
elsif ( $desc->{txt} ) {
$txt_long = '%value%' . chr(0x00A0) . '%txt%';
$txt_long = $desc->{tmpl_long}
if ( $desc->{tmpl_long} );
}
# for either plural or singular long text string
if ($txt_long) {
# Replace all %text% placeholders with text value found
# in old style READINGS.
# Normally only TIME & VAL would be available but some modules might
# have extended this and we want people to be able to make use of it.
if ( $r && $reading && $r->{$reading} ) {
foreach my $k ( keys %{ $r->{$reading} } ) {
$txt_long =~ s/%$k%/$r->{$reading}{$k}/g;
}
}
# Replace all %text% placeholders with text value found
# in matching reading desc hash keys
foreach my $k ( keys %{$desc} ) {
my $vdm = $desc->{$k};
$vdm = UConv::decimal_mark( $vdm, $desc->{decimal_mark} )
if ( defined( $desc->{decimal_mark} ) );
$txt_long =~ s/%$k%/$vdm/g;
}
}
return ( Encode::encode_utf8($txt), Encode::encode_utf8($txt_long) );
}
# format a number according to desc and optional format.
sub formatValue($$$;$$$$) {
my ( $device, $reading, $value, $desc, $format, $scope, $lang ) = @_;
$lang = "en" if ( !$lang );
my $lang_base = $lang;
$lang_base =~ s/^(\w+)(_.*)$/$1/;
my $value_num;
return $value
if (!defined($value)
|| ref($value)
|| AttrVal( $device, "showUnits", 1 ) eq "0" );
$desc = readingsDesc( $device, $reading )
if ( !$desc || !ref($desc) );
return $value
if ( !$format && ( !$desc || ref($desc) ne 'HASH' )
|| keys %{$desc} < 1 );
my $llvl = ( defined( $desc->{verbose} ) ? $desc->{verbose} : 4 );
# source value language
my $slang = "en";
$slang = $desc->{lang} if ( $desc && $desc->{lang} );
my $slang_base = $slang;
$slang_base =~ s/^(\w+)(_.*)$/$1/;
# factor
if ( $desc && $desc->{factor} ) {
if ( looks_like_number($value) ) {
$value *= $desc->{factor};
}
else {
Log3 $device, $llvl,
"formatValue($device:$reading,rtype=$desc->{rtype}): factor "
. "multiplication failed - '$value' is not a number";
}
}
$format = $desc->{format} if ( !$format && $desc && $desc->{format} );
$scope = $desc->{scope} if ( !$scope && $desc && $desc->{scope} );
# language handling for scope
if ( ref($scope) eq "HASH" && defined( $scope->{$slang} ) ) {
my $v;
$v = $scope->{$slang};
$scope = undef;
$scope = $v;
}
elsif ( ref($scope) eq "HASH" && defined( $scope->{$slang_base} ) ) {
my $v;
$v = $scope->{$slang_base};
$scope = undef;
$scope = $v;
}
################################
# scope:
# Check for value to be in correct scope
#
# Use user defined subroutine
if ( ref($scope) eq 'CODE' && &$scope ) {
( $value, $value_num ) = $scope->($value);
}
# scope was defined as HASH
elsif ( ref($scope) eq 'HASH' ) {
my $log;
if ( !looks_like_number($value) ) {
if ( $scope->{regex} ) {
$log .= "'$value' does not match regex $scope. "
if ( $value !~ /$scope->{regex}/ );
$value_num = $scope->{value_num}
if ( defined( $scope->{value_num} ) );
}
if ( $scope->{eq} && $value ne $scope->{eq} ) {
$value = $scope->{eq}
if ( !$scope->{keep} || $scope->{strict} );
$log .= "$value is not equal to $scope->{eq}. ";
}
if ( !$scope->{regex} && !$scope->{eq} ) {
$log = "'$value' is not a number"
if ( !$scope->{empty} && !$scope->{empty_replace} );
$value = "0" if ( !$scope->{keep} && !$scope->{empty_replace} );
$value = $scope->{empty_replace}
if ( defined( $scope->{empty_replace} ) );
}
}
elsif ( $scope->{minValue} && $scope->{maxValue} ) {
if ( abs($value) < $scope->{minValue} ) {
$value = $scope->{minValue}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is smaller than $scope->{minValue}";
}
if ( abs($value) > $scope->{maxValue} ) {
$value = $scope->{maxValue}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is higher than $scope->{maxValue}";
}
}
elsif ( $scope->{lt} && $scope->{gt} ) {
if ( abs($value) < $scope->{lt} ) {
$value = $scope->{lt}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is less than $scope->{lt}";
}
if ( abs($value) > $scope->{gt} ) {
$value = $scope->{gt}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is greater than $scope->{gt}";
}
}
elsif ( $scope->{le} && $scope->{ge} ) {
if ( abs($value) <= $scope->{le} ) {
$value = $scope->{le}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is less or equal than $scope->{le}";
}
if ( abs($value) >= $scope->{ge} ) {
$value = $scope->{ge}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is geater or qual than $scope->{ge}";
}
}
elsif ( $scope->{minValue} && abs($value) < $scope->{minValue} ) {
$value = $scope->{minValue}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is smaller than $scope->{minValue}";
}
elsif ( $scope->{lt} && abs($value) < $scope->{lt} ) {
$value = $scope->{lt} if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is less than $scope->{lt}";
}
elsif ( $scope->{maxValue} && abs($value) > $scope->{maxValue} ) {
$value = $scope->{maxValue}
if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is higher than $scope->{maxValue}";
}
elsif ( $scope->{gt} && abs($value) > $scope->{gt} ) {
$value = $scope->{gt} if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is greater than $scope->{gt}";
}
elsif ( $scope->{ge} && abs($value) >= $scope->{ge} ) {
$value = $scope->{ge} if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is greater or equal than $scope->{ge}";
}
elsif ( $scope->{le} && abs($value) <= $scope->{le} ) {
$value = $scope->{le} if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is less or equal than $scope->{le}";
}
elsif ( $scope->{eq} && $value ne $scope->{eq} ) {
$value = $scope->{eq} if ( !$scope->{keep} || $scope->{strict} );
$log = "$value is not equal to $scope->{eq}";
}
Log3 $device, $llvl,
"formatValue($device:$reading,rtype=$desc->{rtype}) out of scope: $log"
if ($log);
}
# scope was defined as ARRAY so let's assume value
# to be the index for that array to convert
# this index to a text string.
# 'value_num' will be defined here to help harmonising text values over
# different reading names representing similar/comparable content in
# some way.
elsif ( ref($scope) eq 'ARRAY' ) {
# value found as index within array.
# assuming this scope was defined as string in regex format
if ( looks_like_number($value)
&& defined( $scope->[$value] )
&& !ref( $scope->[$value] )
&& $value =~ /$scope->[$value]/gmi )
{
$value_num = $value;
# some language handling
# to replace original value with language specific
# and FHEM harmonised value
#
# If specified language was found
if ( ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{$lang} ) eq "ARRAY"
&& defined( $desc->{txt}{$lang}[$value] ) )
{
$value = $desc->{txt}{$lang}[$value];
}
# also try base language
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{$lang_base} ) eq "ARRAY"
&& defined( $desc->{txt}{$lang_base}[$value] ) )
{
$value = $desc->{txt}{$lang_base}[$value];
}
# fallback to english
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{en} ) eq "ARRAY"
&& defined( $desc->{txt}{en}[$value] ) )
{
$value = $desc->{txt}{en}[$value];
}
# if there is no language defined at all
elsif ( ref( $desc->{txt} ) eq "ARRAY"
&& defined( $desc->{txt}[$value] ) )
{
$value = $desc->{txt}[$value];
}
# if there is no txt definition at all
# arrays are unbalanced
else {
Log3 $device, 3,
"formatValue($device:$reading:$desc->{rtype}): "
. "ERROR - unbalanced number of items in arrays";
}
}
else {
my $i = 0;
foreach ( @{$scope} ) {
if ( $value =~ /$_/gmi ) {
$value_num = $i;
if ( ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{$lang} ) eq "ARRAY"
&& defined( $desc->{txt}{$lang}[$i] ) )
{
$value = $desc->{txt}{$lang}[$i];
}
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{$lang_base} ) eq "ARRAY"
&& defined( $desc->{txt}{$lang_base}[$i] ) )
{
$value = $desc->{txt}{$lang_base}[$i];
}
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{en} ) eq "ARRAY"
&& defined( $desc->{txt}{en}[$i] ) )
{
$value = $desc->{txt}{en}[$i];
}
elsif ( ref( $desc->{txt} ) eq "ARRAY"
&& defined( $desc->{txt}[$i] ) )
{
$value = $desc->{txt}[$i];
}
elsif ( !ref( $desc->{txt} ) && defined( $desc->{txt} ) ) {
$value = $desc->{txt};
}
else {
$value = $1 if ( defined($1) );
if ( !defined($1) ) {
Log3 $device, $llvl,
"formatValue($device:$reading:$desc->{rtype}) out of scope: "
. "missing txt value or regex output";
$value = $scope->[$i];
}
}
last;
}
$i++;
}
}
}
# scope was defined as string, let's assume it's in regex format
elsif ( defined($scope) && $scope ne "" && $value =~ /$scope/gmi ) {
# if regex matches and returns a $1, let's assume this is
# by intention to replace something
if ( defined($1) ) {
Log3 $device, $llvl,
"formatValue($device:$reading:$desc->{rtype}): "
. "'$value' replaced by regex $scope with result from variable \$1";
$value = $1;
}
}
# scope definition present but value seems to be out of regex scope
elsif ( defined($scope) && $scope ne "" ) {
Log3 $device, $llvl,
"formatValue($device:$reading:$desc->{rtype}) out of scope: "
. "'$value' does not match regex $scope";
}
# format
#
if ( $format && !looks_like_number($value) ) {
Log3 $device, $llvl,
"formatValue($device:$reading,$desc->{rtype}) cannot re-format: $value is not a number"
if ( ref($scope) eq 'HASH'
&& !$scope->{empty}
&& !$scope->{empty_replace} );
}
elsif ( ref($format) eq 'CODE' && &$format ) {
$value = $format->($value);
}
elsif ( ref($format) eq 'HASH' ) {
my $v = abs($value);
foreach my $l ( sort { $b <=> $a } keys( %{$format} ) ) {
next
if ( ref( $format->{$l} ) ne 'HASH'
|| !$format->{$l}{rescale} );
if ( $v >= $l ) {
my $rescale = $format->{$l}{rescale};
$value *= $rescale if ($rescale);
$value = sprintf( $format->{$l}{format}, $value )
if ( $format->{$l}{format} );
last;
}
}
}
elsif ( ref($format) eq 'ARRAY' ) {
Log3 $device, $llvl, "formatValue($device:$reading:$desc->{rtype})"
. " format not implemented: ARRAY";
}
elsif ($format) {
my $rescale = $desc->{rescale};
$value *= $rescale if ($rescale);
$value = sprintf( $format, $value );
}
$desc->{value}{$lang} = $value;
$desc->{value_num} = $value_num if ( defined($value_num) );
my ( $txt, $txt_long ) =
replaceTemplate( $device, $reading, $desc, $lang, $value );
$desc->{value_txt}{$lang} = $txt;
$desc->{value_txt_long}{$lang} = $txt_long if ( defined($txt_long) );
delete $desc->{value_txt_long}{$lang}
if ( !defined($txt_long) && defined( $desc->{value_txt_long}{$lang} ) );
return ( $txt, $txt_long, $value, $value_num ) if (wantarray);
return $value
if ( ( defined( $desc->{showUnits} ) && $desc->{showUnits} eq "2" )
|| AttrVal( $device, "showUnits", 1 ) eq "2" );
return $txt_long
if ( $desc->{showLong} && !$desc->{showShort} );
return $txt;
}
# find desc for device:reading
sub readingsDesc($;$) {
my ( $device, $reading ) = @_;
my $desc = getCombinedKeyValAttr( $device, "readingsDesc", $reading );
my $rtype;
$rtype = $desc->{rtype} if ( $desc->{rtype} );
if ( $rtype && defined( $rtypes->{$rtype} ) ) {
# copy information from other hashes until 3rd level
foreach my $k ( keys %{ $rtypes->{$rtype} } ) {
if ( ref( $rtypes->{$rtype}{$k} ) eq "HASH" ) {
foreach my $k2 ( keys %{ $rtypes->{$rtype}{$k} } ) {
if ( ref( $rtypes->{$rtype}{$k}{$k2} ) eq "HASH" ) {
foreach ( keys %{ $rtypes->{$rtype}{$k}{$k2} } ) {
delete $desc->{$k}{$k2}{$_}
if ( $desc->{$k}{$k2}{$_} );
$desc->{$k}{$k2}{$_} =
$rtypes->{$rtype}{$k}{$k2}{$_};
}
}
else {
delete $desc->{$k}{$k2} if ( $desc->{$k}{$k2} );
$desc->{$k}{$k2} = $rtypes->{$rtype}{$k}{$k2};
}
}
}
else {
delete $desc->{$k} if ( $desc->{$k} );
$desc->{$k} = $rtypes->{$rtype}{$k};
}
}
foreach ( 'ref', 'ref_t', 'ref_sq', 'ref_cu' ) {
my $suffix = $_;
$suffix =~ s/^[a-z]+//;
if ( defined( $desc->{$_} ) ) {
my $ref = $desc->{$_};
if ( !defined( $rtypes->{$ref} ) ) {
Log 1, "readingsDesc($rtype) broken reference $_";
next;
}
foreach my $k ( keys %{ $rtypes->{$ref} } ) {
next
if ( $k =~ /^scale/ )
; # exclude scales from referenced rtype
if ( !defined( $desc->{$k} ) ) {
$desc->{$k} = $rtypes->{$ref}{$k};
}
else {
$desc->{ $k . $suffix } = $rtypes->{$ref}{$k}
if ( !defined( $desc->{ $k . $suffix } ) );
}
}
}
}
if ( $desc->{scale_m} ) {
my $ref = $desc->{scale_m};
foreach my $k ( keys %{ $scales_m->{$ref} } ) {
$desc->{$k} = $scales_m->{$ref}{$k}
if ( !defined( $desc->{$k} ) );
}
}
if ( $desc->{scale_sq} ) {
foreach my $k ( keys %{$scales_sq} ) {
$desc->{$k} = $scales_sq->{$k}
if ( !defined( $desc->{$k} ) );
}
my $ref = $desc->{scale_sq};
foreach my $k ( keys %{ $scales_m->{$ref} } ) {
$desc->{ $k . "_sq" } = $scales_m->{$ref}{$k}
if ( !defined( $desc->{ $k . "_sq" } ) );
}
}
if ( $desc->{scale_cu} ) {
foreach my $k ( keys %{$scales_cu} ) {
$desc->{$k} = $scales_cu->{$k}
if ( !defined( $desc->{$k} ) );
}
my $ref = $desc->{scale_cu};
foreach my $k ( keys %{ $scales_m->{$ref} } ) {
$desc->{ $k . "_cu" } = $scales_m->{$ref}{$k}
if ( !defined( $desc->{ $k . "_cu" } ) );
}
}
$desc->{ref_base} = 999 if ( !defined( $desc->{ref_base} ) );
my $ref = $desc->{ref_base};
foreach my $k ( keys %{ $rtype_base->{$ref} } ) {
$desc->{$k} = $rtype_base->{$ref}{$k}
if ( !defined( $desc->{$k} ) );
}
}
return $desc;
}
# format device:reading with optional default value and optional desc and optional format
sub formatReading($$;$$$$$) {
my ( $device, $reading, $default, $desc, $format, $scope, $lang ) = @_;
$desc = readingsDesc( $device, $reading ) if ( !$desc );
my $value = ReadingsVal( $device, $reading, undef );
$value = $default if ( !defined($value) );
return formatValue( $device, $reading, $value, $desc, $format, $scope,
$lang );
}
# return unit symbol for device:reading
sub readingsUnit($$;$$$) {
my ( $device, $reading, $long, $combined, $desc ) = @_;
$desc = readingsDesc( $device, $reading ) if ( !$desc );
return (
$desc->{suffix} ? $desc->{suffix} : undef,
$desc->{symbol} ? $desc->{symbol} : undef,
$desc->{txt} ? $desc->{txt} : undef
) if (wantarray);
my ( $txt, $txt_long, $value, $value_num ) =
formatReading( $device, $reading, "", $desc );
$txt =~ s/\s*$value\s*//;
$txt_long =~ s/\s*$value\s*//;
return "$txt_long ($txt)"
if ( $combined
&& defined($txt_long)
&& $txt_long ne ""
&& defined($txt)
&& $txt ne "" );
return $txt_long if ( $long && defined($txt_long) && $txt_long ne "" );
return $txt if ( defined($txt) && $txt ne "" );
return '';
}
# return dimension symbol for device:reading
sub readingsShortname($$) {
my ( $device, $reading ) = @_;
if ( my $desc = readingsDesc( $device, $reading ) ) {
return $desc->{formula_symbol} if ( $desc->{formula_symbol} );
return $desc->{dimension}
if ( $desc->{dimension} && $desc->{dimension} =~ /^[A-Z]+$/ );
return $desc->{symbol} if ( $desc->{symbol} );
}
return $reading;
}
# format device STATE readings according to stateFormat and optional units
sub makeSTATE($;$$) {
my ( $device, $stateFormat, $withUnits ) = @_;
$stateFormat = '' if ( !$stateFormat );
my $hash = $defs{$device};
return $stateFormat if ( !$hash );
$stateFormat = AttrVal( $device, 'stateFormat', undef )
if ( !$stateFormat );
return '' if ( !$stateFormat );
# filter own function name to avoid loops
$stateFormat =~ s/{.*}//mg
if ( $stateFormat =~ /makeSTATE/ );
# use all readings if stateFormat is empty
$stateFormat = join( ' ', sort keys %{ $hash->{READINGS} } )
if ( $stateFormat !~ /\w+/ );
my $txt;
if ( $stateFormat =~ m/^{(.*)}$/ ) {
$stateFormat = eval $1;
if ($@) {
$stateFormat = "Error evaluating $device stateFormat: $@";
Log 1, $stateFormat;
}
}
else {
my $r = $hash->{READINGS};
my %usedShortnames;
while ( $stateFormat =~ /\b([A-Za-z\d_\.-]+):?([A-Za-z\d_\.-]+)?\b/g ) {
$txt .= " " if ($txt);
if ( defined( $r->{$1} ) ) {
my $sname = readingsShortname( $device, $1 );
$usedShortnames{$sname}++ if ( $usedShortnames{$sname} );
$usedShortnames{$sname} = 1 if ( !$usedShortnames{$sname} );
if ( $2 && $2 ne "" ) {
$txt .= "$2: ";
}
elsif ( $usedShortnames{$sname} > 1 ) {
$txt .= "$sname" . $usedShortnames{$sname} . ": ";
}
else {
$txt .= "$sname: ";
}
if ($withUnits) {
$txt .= formatReading( $device, $1 );
}
else {
$txt .= ( formatReading( $device, $1 ) )[2];
}
}
else {
$txt .= $1;
$txt .= ":$2" if ($2);
}
}
return $txt;
}
return $stateFormat;
}
# get combined hash for settings from module, device, global and device attributes
sub getCombinedKeyValAttr($;$$) {
my ( $name, $attribute, $reading ) = @_;
my $d = $defs{$name} if ( $defs{$name} );
my $m = $modules{ $d->{TYPE} } if ( $d && $d->{TYPE} );
my $g = $defs{"global"};
# join hashes until 3rd level
my $desc;
if ( $m && $m->{$attribute} && ref( $m->{$attribute} ) eq "HASH" ) {
foreach my $k ( keys %{ $m->{$attribute} } ) {
if ( ref( $m->{$attribute}{$k} ) eq "HASH" ) {
foreach my $k2 ( keys %{ $m->{$attribute}{$k} } ) {
if ( ref( $m->{$attribute}{$k}{$k2} ) eq "HASH" ) {
foreach ( keys %{ $m->{$attribute}{$k}{$k2} } ) {
delete $desc->{$k}{$k2}{$_}
if ( $desc->{$k}{$k2}{$_} );
$desc->{$k}{$k2}{$_} =
$m->{$attribute}{$k}{$k2}{$_};
}
}
else {
delete $desc->{$k}{$k2} if ( $desc->{$k}{$k2} );
$desc->{$k}{$k2} = $m->{$attribute}{$k}{$k2};
}
}
}
else {
delete $desc->{$_} if ( $desc->{$k} );
$desc->{$_} = $m->{$attribute}{$k};
}
}
}
if ( $g && $g->{$attribute} && ref( $g->{$attribute} ) eq "HASH" ) {
foreach my $k ( keys %{ $g->{$attribute} } ) {
if ( ref( $g->{$attribute}{$k} ) eq "HASH" ) {
foreach my $k2 ( keys %{ $g->{$attribute}{$k} } ) {
if ( ref( $g->{$attribute}{$k}{$k2} ) eq "HASH" ) {
foreach ( keys %{ $g->{$attribute}{$k}{$k2} } ) {
delete $desc->{$k}{$k2}{$_}
if ( $desc->{$k}{$k2}{$_} );
$desc->{$k}{$k2}{$_} =
$g->{$attribute}{$k}{$k2}{$_};
}
}
else {
delete $desc->{$k}{$k2} if ( $desc->{$k}{$k2} );
$desc->{$k}{$k2} = $g->{$attribute}{$k}{$k2};
}
}
}
else {
delete $desc->{$_} if ( $desc->{$k} );
$desc->{$_} = $g->{$attribute}{$k};
}
}
}
if ( $d && $d->{$attribute} && ref( $d->{$attribute} ) eq "HASH" ) {
foreach my $k ( keys %{ $d->{$attribute} } ) {
if ( ref( $d->{$attribute}{$k} ) eq "HASH" ) {
foreach my $k2 ( keys %{ $d->{$attribute}{$k} } ) {
if ( ref( $d->{$attribute}{$k}{$k2} ) eq "HASH" ) {
foreach ( keys %{ $d->{$attribute}{$k}{$k2} } ) {
delete $desc->{$k}{$k2}{$_}
if ( $desc->{$k}{$k2}{$_} );
$desc->{$k}{$k2}{$_} =
$d->{$attribute}{$k}{$k2}{$_};
}
}
else {
delete $desc->{$k}{$k2} if ( $desc->{$k}{$k2} );
$desc->{$k}{$k2} = $d->{$attribute}{$k}{$k2};
}
}
}
else {
delete $desc->{$_} if ( $desc->{$k} );
$desc->{$_} = $d->{$attribute}{$k};
}
}
}
return
if (
keys %{$desc} < 1
|| (
$reading
&& ( !defined( $desc->{$reading} )
|| keys %{ $desc->{$reading} } < 1 )
)
);
return $desc->{$reading} if ($reading);
return $desc;
}
# save key/value pair to device attribute
sub setKeyValAttr($$$$$) {
my ( $name, $attribute, $reading, $key, $value ) = @_;
my $d = $defs{$name} if ( $defs{$name} );
my $ret;
return
if (
!$d
|| ( defined( $d->{$attribute} )
&& defined( $d->{$attribute}{$reading} )
&& defined( $d->{$attribute}{$reading}{$key} )
&& $d->{$attribute}{$reading}{$key} eq $value )
);
# rtype
if ( $key =~ /^rtype$/i ) {
$key = lc($key);
# Show all possible values
if ( $value && $value eq "?" ) {
return "CURRENTLY KNOWN READING TYPES\n\n"
. PrintHash( $rtypes, 0 );
}
# find rtype based on reading name
elsif ( !defined($value) || $value eq "" ) {
$value = rname2rtype( $name, $reading );
$ret =
"Set auto-detected $key for device $name $reading: " . $value
if ($value);
}
my $curr;
no strict "refs";
$curr = &$attribute( $name, $reading ) if (&$attribute);
use strict "refs";
return
if (
!defined($value)
|| $value eq ""
|| ( defined($curr)
&& defined( $curr->{$key} )
&& $curr->{$key} eq $value )
|| ( defined( $d->{$attribute} )
&& defined( $d->{$attribute}{$reading} )
&& defined( $d->{$attribute}{$reading}{$key} )
&& $d->{$attribute}{$reading}{$key} eq $value )
);
return
"Invalid value $value for $key: Cannot be assigned to device $name $reading"
if ( !defined( $rtypes->{$value} ) );
$ret =
"Changed value $key='"
. $d->{$attribute}{$reading}{$key}
. "' for device $name $reading to: "
. $value
if ( defined( defined( $d->{$attribute} ) )
&& defined( $d->{$attribute}{$reading} )
&& defined( $d->{$attribute}{$reading}{$key} )
&& $d->{$attribute}{$reading}{$key} ne $value );
}
$d->{$attribute}{$reading}{$key} = $value;
# write attribute
$Data::Dumper::Terse = 1;
$Data::Dumper::Sortkeys = 1;
my $txt = Dumper( $d->{$attribute} );
$Data::Dumper::Terse = 0;
$Data::Dumper::Sortkeys = 0;
$txt =~ s/(=>\s*\{|['"],?)\s*\n\s*/$1 /gsm;
CommandAttr( undef, "$name $attribute $txt" );
return $ret;
}
sub deleteKeyValAttr($$$;$) {
my ( $name, $attribute, $reading, $key ) = @_;
my $d = $defs{$name} if ( $defs{$name} );
my $rt;
return
if ( !$d
|| !defined( $d->{$attribute} )
|| !defined( $d->{$attribute}{$reading} )
|| ( $key && !defined( $d->{$attribute}{$reading}{$key} ) ) );
if ($key) {
$rt = " $key=" . $d->{$attribute}{$reading}{$key};
delete $d->{$attribute}{$reading}{$key};
}
delete $d->{$attribute}{$reading}
if ( !$key || keys %{ $d->{$attribute}{$reading} } < 1 );
# delete attribute
if ( keys %{ $d->{$attribute} } < 1 ) {
CommandDeleteAttr( undef, "$name $attribute" );
}
# write attribute
else {
$Data::Dumper::Terse = 1;
$Data::Dumper::Sortkeys = 1;
my $txt = Dumper( $d->{$attribute} );
$Data::Dumper::Terse = 0;
$Data::Dumper::Sortkeys = 0;
$txt =~ s/(=>\s*\{|[\'\"0-9],?)\s*\n\s*/$1 /gsm;
CommandAttr( undef, "$name $attribute $txt" );
}
return "Removed $reading$rt from attribute $name $attribute";
}
################################################################
#
# Wrappers for commonly used core functions in device-specific modules.
#
################################################################
# Generalized function for DbLog rtype support
sub Unit_DbLog_split($$) {
my ( $event, $name ) = @_;
my ( $reading, $value, $unit ) = "";
# exclude any multi-value events
if ( $event =~ /(.*: +.*: +.*)+/ ) {
Log3 $name, 5,
"Unit_DbLog_split $name: Ignoring multi-value event $event";
return undef;
}
# exclude sum/cum and avg events
elsif ( $event =~ /^.*(min|max|avg|sum|cum|avg\d+m|sum\d+m|cum\d+m): +.*/ )
{
Log3 $name, 5, "Unit_DbLog_split $name: Ignoring sum/avg event $event";
return undef;
}
# automatic text conversions through reading type
elsif ( $event =~ /^(.+): +(\S+) *(.*)/ ) {
$reading = $1;
my ( $txt, $txt_long, $val, $val_num ) =
formatReading( $name, $reading, "" );
if ( defined($txt) && defined($reading) && defined($val) ) {
$txt =~ s/\s*$val\s*//;
$txt_long =~ s/\s*$val\s*//;
$value = defined($val_num) ? $val_num : $val;
$unit = "$txt_long ($txt)" if ($txt_long);
$unit = "$txt" if ( !$txt_long );
}
}
# general event handling
if ( !defined($value)
&& $event =~ /^(.+): +(\S+) *[\[\{\(]? *([\w\°\%\^\/\\]*).*/
&& defined($1)
&& defined($2) )
{
$reading = $1;
$value = ReadingsNum( $name, $1, $2 );
$unit = defined($3) ? $3 : "";
}
if ( !looks_like_number($value) ) {
Log3 $name, 5,
"Unit_DbLog_split $name: Ignoring event $event: value $value does not look like a number";
return undef;
}
Log3 $name, 5,
"Unit_DbLog_split $name: Splitting event $event > reading=$reading value=$value unit=$unit";
return ( $reading, $value, $unit );
}
################################################################
#
# User commands
#
################################################################
# command: setreadingdesc
my %setreadingdeschash = (
Fn => "CommandSetReadingDesc",
Hlp =>
"<devspec> <readingspec> [noCheck] <key>=[<value>|?],set reading rtype information for <devspec> <reading>",
);
$cmds{setreadingdesc} = \%setreadingdeschash;
sub CommandSetReadingDesc($@) {
my ( $cl, $def ) = @_;
my $attribute = "readingsDesc";
my $namedef =
"where <devspec> is a single device name, a list separated by comma (,) or a regexp. See the devspec section in the commandref.html for details.\n"
. "<readingspec> can be a single reading name, a list separated by comma (,) or a regexp.";
my ( $a, $h ) = parseParams($def);
$a->[0] = ".*" if ( !$a->[0] );
$a->[1] = ".*" if ( !$a->[1] );
return
"Usage: setreadingdesc <devspec> <readingspec> [noCheck] <key>=[<value>|?]\n$namedef"
if ( $a->[0] eq "?" || $a->[1] eq "?" || !%{$h} );
my @rets;
my $last;
foreach my $name ( devspec2array( $a->[0], $cl ) ) {
if ( !defined( $defs{$name} ) ) {
push @rets, "Please define $name first";
next;
}
# do not check for existing reading
if ( $name eq "global"
|| ( defined( $a->[2] ) && $a->[2] =~ /nocheck/i ) )
{
foreach ( keys %$h ) {
my $ret =
setKeyValAttr( $name, $attribute, $a->[1], $_, $h->{$_} );
push @rets, $ret if ( defined($ret) );
$last = 1 if ( $h->{$_} eq "?" || $h->{$_} eq "" );
}
next;
}
# check for existing reading
my $readingspec = '^' . $a->[1] . '$';
foreach my $reading (
grep { /$readingspec/ }
keys %{ $defs{$name}{READINGS} }
)
{
foreach ( keys %$h ) {
my $ret =
setKeyValAttr( $name, $attribute, $reading, $_, $h->{$_} );
push @rets, $ret if ( defined($ret) );
$last = 1 if ( $h->{$_} eq "?" || $h->{$_} eq "" );
}
}
last if ($last);
}
return join( "\n", @rets );
}
# command: deletereadingdesc
my %deletereadingdeschash = (
Fn => "CommandDeleteReadingDesc",
Hlp =>
"<devspec> <readingspec> [<keyspec>],delete key for <devspec> <reading>",
);
$cmds{deletereadingdesc} = \%deletereadingdeschash;
sub CommandDeleteReadingDesc($@) {
my ( $cl, $def ) = @_;
my $attribute = "readingsDesc";
my $namedef =
"where <devspec> is a single device name, a list separated by comma (,) or a regexp. See the devspec section in the commandref.html for details.\n"
. "<readingspec> and <keyspec> can be a single reading name, a list separated by comma (,) or a regexp.";
my ( $a, $h ) = parseParams($def);
$a->[0] = ".*" if ( !$a->[0] );
$a->[1] = ".*" if ( !$a->[1] );
$a->[2] = ".*" if ( !$a->[2] );
return
"Usage: deletereadingdesc <devspec> <readingspec> [<keyspec>]\n$namedef"
if ( $a->[0] eq "?" || $a->[1] eq "?" );
my @rets;
my $last;
foreach my $name ( devspec2array( $a->[0], $cl ) ) {
if ( !defined( $defs{$name} ) ) {
push @rets, "Please define $name first";
next;
}
my $readingspec = '^' . $a->[1] . '$';
foreach my $reading (
grep { /$readingspec/ }
keys %{ $defs{$name}{$attribute} }
)
{
my $keyspec = '^' . $a->[2] . '$';
foreach my $key (
grep { /$keyspec/ }
keys %{ $defs{$name}{$attribute}{$reading} }
)
{
my $ret = deleteKeyValAttr( $name, $attribute, $reading, $key );
push @rets, $ret if ( defined($ret) );
}
}
}
return join( "\n", @rets );
}
1;