###############################################################################
# $Id$
package main;
use strict;
use warnings;
# use locale;
use POSIX;
use Data::Dumper;
use utf8;
use Encode qw(encode_utf8 decode_utf8);
use UConv;
# only to suppress file reload error in FHEM
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',
},
},
'1.0e9' => {
'scale_txt_m' => 'G',
'scale_txt_long_m' => {
de => 'Giga',
en => 'Giga',
},
},
'1.0e12' => {
'scale_txt_m' => 'T',
'scale_txt_long_m' => {
de => 'Tera',
en => 'Tera',
},
},
'1.0e15' => {
'scale_txt_m' => 'P',
'scale_txt_long_m' => {
de => 'Peta',
en => 'Peta',
},
},
};
# 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 cubic 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
's' => {
'scale_txt_t' => 's',
'scale_txt_long_t' => {
de => 'Sekunde',
en => 'second',
},
'scale_txt_long_pl_t' => {
de => 'Sekunden',
en => 'seconds',
},
},
# minute
'min' => {
'scale_txt_t' => {
de => 'Min',
en => 'min',
},
'scale_txt_long_t' => {
de => 'Minute',
en => 'minute',
},
'scale_txt_long_pl_t' => {
de => 'Minuten',
en => 'minutes',
},
},
# hour
'h' => {
'scale_txt_t' => 'h',
'scale_txt_long_t' => {
de => 'Stunde',
en => 'hour',
},
'scale_txt_long_pl_t' => {
de => 'Stunden',
en => 'hours',
},
},
# day
'd' => {
'scale_txt_t' => 'd',
'scale_txt_long_t' => {
de => 'Tag',
en => 'day',
},
'scale_txt_long_pl_t' => {
de => 'Tage',
en => 'days',
},
},
# week
'w' => {
'scale_txt_t' => 'w',
'scale_txt_long_t' => {
de => 'Woche',
en => 'week',
},
'scale_txt_long_pl_t' => {
de => 'Wochen',
en => 'weeks',
},
},
# month
'm' => {
'scale_txt_t' => 'm',
'scale_txt_long_t' => {
de => 'Monat',
en => 'month',
},
'scale_txt_long_pl_t' => {
de => 'Monate',
en => 'month',
},
},
# year
'a' => {
'scale_txt_t' => 'a',
'scale_txt_long_t' => {
de => 'Jahr',
en => 'year',
},
'scale_txt_long_pl_t' => {
de => 'Jahre',
en => 'years',
},
},
};
# 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 => 'kmph',
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 => 'M L2 T−3 I−2',
formula_symbol => 'R',
rtype_base => 'ohm',
base_description => {
de => 'elektrischer 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; normally combined with a scale_sq reference as well
# ref_cu => include keys from $scales_cu here; normally combined with a scale_cu reference as well
# ref_t => reference to $scales_t id to include it's keys here; ; normally combined with a scale_t reference as well
# 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 => '%value%',
},
url_http => {
ref_base => 900,
rtype_description => 'HTTP/S URL w/ or w/o protocol prefix',
tmpl => '%value%',
},
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 => [
'^(=|steady|stable|0)$', '^(\+|rising|up|1)$',
'^(-|falling|down|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', 'So' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
},
txt => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
},
txt_long => {
de => [
'Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'
],
en => [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday', 'Sunday'
],
},
scope => {
de => [
'^(So|Son|Sonntag|0|7)$', '^(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|7)$', '^(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', 'So' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
},
txt => {
de => [ 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So' ],
en => [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],
},
txt_long => {
de => [
'Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'
],
en => [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday', 'Sunday'
],
},
scope => {
de => [
'^(So|Son|Sonntag|7)$', '^(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|7)$', '^(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 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',
ref => 'gon',
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'
],
nl => [
'N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO',
'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW'
],
fr => [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO'
],
pl => [
'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'
],
nl => [
'N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO',
'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW'
],
fr => [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO'
],
pl => [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
],
},
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(ü|u|ue)dost|5)$',
value_num => 112.5,
},
{
ge => 135.0,
lt => 157.5,
regex => '^(SO|S(ü|u|ue)dost|6)$',
value_num => 135.0,
},
{
ge => 157.5,
lt => 180.0,
regex => '^(SSO|S(ü|u|ue)d-S(ü|u|ue)dost|7)$',
value_num => 157.5,
},
{
ge => 180.0,
lt => 202.5,
regex => '^(S|S(ü|u|ue)den|8)$',
value_num => 180.0,
},
{
ge => 202.5,
lt => 225.0,
regex => '^(SSW|S(ü|u|ue)d-S(ü|u|ue)dwest|9)$',
value_num => 202.5,
},
{
ge => 225.0,
lt => 247.5,
regex => '^(SW|S(ü|u|ue)dwest|10)$',
value_num => 225.0,
},
{
ge => 247.5,
lt => 270.0,
regex => '^(WSW|West-S(ü|u|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 => [
'^((nt_)?clear|nt_sunny|0)$', '^(sunny|1)$',
'^((nt_)?cloudy|2)$', '^((nt_)?rain|3)$'
],
rtype_description => {
de => 'Wetterbedingung',
en => 'weather condition',
},
},
condition_hum => {
ref_base => 900,
txt => {
de => [ 'trocken', 'niedrig', 'optimal', 'hoch', 'feucht' ],
en => [ 'dry', 'low', 'ideal', 'high', 'wet' ],
},
scope => [
'^(dry|0)$', '^(low|1)$',
'^(ideal|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',
},
},
# color
rgbhex => {
ref_base => 900,
rtype_description => {
de => 'RGB Farbwert in Hex Notation',
en => 'RGB color value in Hex notation',
},
scope => '^#?(([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2}))$',
},
rgb => {
ref_base => 900,
rtype_description => {
de => 'RGB Farbwert in Dezimal Notation',
en => 'RGB color value in decimal notation',
},
scope =>
'^[\s\t\n ]*(?:rgb|RGB)?[\s\t\n ]*\(?[\s\t\n ]*((?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5])[\s\t\n ]*,?[\s\t\n ]*)(?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5])[\s\t\n ]*,?[\s\t\n ]*)(?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5])))[\s\t\n ]*\)?[\s\t\n ]*$',
},
# logical operators
bool => {
ref_base => 26,
txt => {
de => [ 'falsch', 'wahr' ],
en => [ 'false', 'true' ],
},
scope => [ '^(false|n|no|0)$', '^(true|y|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 yes/no',
},
},
# decimal 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',
tmpl => '%value%' . chr(0x202F) . '%symbol%',
tmpl_long => '%value% %txt%',
txt => {
de => 'Prozent',
en => 'percent',
},
scope => { minValue => 0, maxValue => 100 },
},
# binary numbering
bin => {
rtype_description => {
de => 'Binärnummer',
en => 'Binary number',
},
scope => '^[01]+$',
},
# octal numbering
oct => {
rtype_description => {
de => 'Oktalnummer',
en => 'Octal number',
},
scope => '^[0-7]+$',
},
# hexadecimal numbering
hex => {
rtype_description => {
de => 'Hexadezimalnummer',
en => 'Hexadecimal number',
},
scope => '^[0-9a-fA-F]+$',
},
# 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%',
decimal_mark => {
de => 2,
en => 1,
},
format => '%i',
},
rad => {
ref_base => 14,
suffix => 'rad',
txt => {
de => 'Radiant',
en => 'radiant',
},
decimal_mark => {
de => 2,
en => 1,
},
format => '%i',
},
# 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',
format => '%.2f',
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 => 's',
suffix => {
de => 's',
en => 's',
},
txt => {
de => 'Sekunde',
en => 'second',
},
txt_pl => {
de => 'Sekunden',
en => 'seconds',
},
},
min => {
ref_base => 2,
scale_t => 'min',
suffix => {
de => 'Min',
en => 'min',
},
txt => {
de => 'Minute',
en => 'minute',
},
txt_pl => {
de => 'Minuten',
en => 'minutes',
},
},
hr => {
ref_base => 2,
scale_t => 'h',
suffix => 'h',
txt => {
de => 'Stunde',
en => 'hour',
},
txt_pl => {
de => 'Stunden',
en => 'hours',
},
},
d => {
ref_base => 2,
scale_t => 'd',
suffix => {
de => 'T',
en => 'd',
},
txt => {
de => 'Tag',
en => 'day',
},
txt_pl => {
de => 'Tage',
en => 'days',
},
},
w => {
ref_base => 2,
scale_t => 'w',
suffix => {
de => 'W',
en => 'w',
},
txt => {
de => 'Woche',
en => 'week',
},
txt_pl => {
de => 'Wochen',
en => 'weeks',
},
},
mon => {
ref_base => 2,
scale_t => 'm',
suffix => {
de => 'M',
en => 'm',
},
txt => {
de => 'Monat',
en => 'month',
},
txt_pl => {
de => 'Monate',
en => 'Monat',
},
},
y => {
ref_base => 2,
scale_t => 'a',
suffix => {
de => 'J',
en => 'y',
},
txt => {
de => 'Jahr',
en => 'year',
},
txt_pl => {
de => 'Jahre',
en => 'years',
},
},
epoch => {
ref_base => 2,
scale_t => 's',
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%',
},
},
kmph => {
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',
},
# logarithmic scale
b => {
ref_base => 19,
scale_m => '1.0e0',
suffix => 'B',
txt => {
de => 'Bel',
en => 'Bel',
},
},
db => {
ref => 'b',
scale_m => '1.0e-1',
},
# electric current
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 => 'W',
txt => {
de => 'Watt',
en => 'Watt',
},
},
va => {
ref => 'w',
},
kw => {
ref => 'w',
scale_m => '1.0e3',
},
megaw => {
ref => 'w',
scale_m => '1.0e6',
},
whr => {
base_ref => 7,
ref => 'w',
scale_m => '1.0e0',
ref_t => 'hr',
scale_t => 'h',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt%',
en => '%value%' . chr(0x00A0) . '%txt%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl%',
en => '%value%' . chr(0x00A0) . '%txt_pl%',
},
rtype_description => {
de => 'Wattstunde',
en => 'Watt hour',
}
},
kwhr => {
base_ref => 7,
ref => 'w',
scale_m => '1.0e3',
ref_t => 'hr',
scale_t => 'h',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt%',
en => '%value%' . chr(0x00A0) . '%txt%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt_pl%',
en => '%value%' . chr(0x00A0) . '%txt_pl%',
},
rtype_description => {
de => 'Kilowattstunde',
en => 'Kilowatt hour',
}
},
mwhr => {
base_ref => 7,
ref => 'w',
scale_m => '1.0e6',
ref_t => 'hr',
scale_t => 'h',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%%suffix_t%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt%%txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt%%txt_pl%',
en => '%value%' . chr(0x00A0) . '%txt% %txt_pl%',
},
rtype_description => {
de => 'Megawattstunde',
en => 'Megawatt hour',
}
},
gwhr => {
base_ref => 7,
ref => 'w',
scale_m => '1.0e9',
ref_t => 'hr',
scale_t => 'h',
format => '%.0f',
tmpl => '%value%' . chr(0x00A0) . '%suffix%%suffix_t%',
tmpl_long => {
de => '%value%' . chr(0x00A0) . '%txt%%txt_t%',
en => '%value%' . chr(0x00A0) . '%txt% %txt_t%',
},
tmpl_long_pl => {
de => '%value%' . chr(0x00A0) . '%txt%%txt_pl%',
en => '%value%' . chr(0x00A0) . '%txt% %txt_pl%',
},
rtype_description => {
de => 'Gigawattstunde',
en => 'Gigawatt hour',
}
},
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',
},
brightness => {
rtype => 'lm',
},
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',
},
luminance => {
rtype => 'lm',
},
motion => {
rtype => 'yesno',
},
pct => {
rtype => 'pct',
},
presence => {
rtype => 'reachable',
},
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',
},
reachable => {
rtype => 'yesno',
},
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;
# reading name exactly matches rtype
return $reading if ( $rtypes->{$reading} && !wantarray );
# remove some prefix or other values to
# flatten reading name
$r =~ s/^h?fc\d+_//i;
$r =~ s/_(min|max|avg|sum|cum|min\d+m|max\d+m|avg\d+m|sum\d+m|cum\d+m)_/_/i;
$r =~ s/^(min|max|avg|sum|cum|min\d+m|max\d+m|avg\d+m|sum\d+m|cum\d+m)_//i;
$r =~ s/_(min|max|avg|sum|cum|min\d+m|max\d+m|avg\d+m|sum\d+m|cum\d+m)$//i;
$r =~ s/.*[-_](temp)$/$1/i;
# rename capital letter containing readings
unless ( defined( $readingsDB->{global}{ lc($r) } ) ) {
$r =~ s/^([A-Z])(.*)/\l$1$2/;
$r =~ s/([A-Z][a-z0-9]+)[\/\|\-_]?/_$1/g;
}
$r = lc($r);
if ( defined( $readingsDB->{global}{$r} ) ) {
# known aliasname reading names
if ( $readingsDB->{global}{$r}{aliasname} ) {
my $dr = $readingsDB->{global}{$r}{aliasname};
$return{aliasname} = $dr;
$rt = (
$readingsDB->{global}{$dr}{rtype}
? $readingsDB->{global}{$dr}{rtype}
: undef
);
}
# known standard reading names
else {
$return{aliasname} = $reading;
$rt = (
$readingsDB->{global}{$r}{rtype}
? $readingsDB->{global}{$r}{rtype}
: undef
);
}
}
# just guessing the rtype from reading name format
elsif ( $r =~ /^.*_([A-Za-z0-9]+)$/i ) {
$return{guess} = 1;
$rt = $1;
}
if (wantarray) {
return ( $reading, $return{aliasname}, $return{guess} )
if ( $rtypes->{$reading} );
return ( $rt, $return{aliasname}, $return{guess} );
}
return $rt if ( $rt && $rtypes->{$rt} );
return undef;
}
# find devices based on the reading
# types they offer
sub rtype2dev {
my %ret;
foreach my $rtype (@_) {
foreach my $d ( keys %defs ) {
next
unless ( defined( $defs{$d}{READINGS} )
&& ref( $defs{$d}{READINGS} ) eq 'HASH' );
my $readingsDescr =
defined( $defs{$d}{TYPE} )
&& defined( $modules{ $defs{$d}{TYPE} } )
&& defined( $modules{ $defs{$d}{TYPE} }{readingsDesc} )
? $modules{ $defs{$d}{TYPE} }{readingsDesc}
: undef;
# if this device's module has defined readingsDesc
if ( $readingsDescr
&& ref($readingsDescr) eq 'HASH' )
{
foreach my $rn ( keys %{$readingsDescr} ) {
next
unless ( defined( $defs{$d}{READINGS}{$rn} )
&& defined( $readingsDescr->{$rn}{rtype} )
&& defined( $rtypes->{ $readingsDescr->{$rn}{rtype} } )
&& $rtype eq $readingsDescr->{$rn}{rtype} );
push @{ $ret{$d} }, $rn
unless ( grep ( /^$rn$/, @{ $ret{$d} } ) );
}
}
# try to find by commonly known reading names
else {
foreach my $rn ( keys %{ $defs{$d}{READINGS} } ) {
my $rt = rname2rtype( $d, $rn );
push @{ $ret{$d} }, $rn
if ( $rt
&& $rt eq $rtype
&& !grep ( /^$rn$/, @{ $ret{$d} } ) );
}
}
}
}
if (wantarray) {
my @reta;
foreach my $d ( sort { "\L$a" cmp "\L$b" } keys %ret ) {
foreach my $r ( sort { "\L$a" cmp "\L$b" } @{ $ret{$d} } ) {
push @reta, "$d:$r";
}
}
return @reta;
}
return \%ret;
}
######################################
# 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 "" || !defined( $odesc->{rtype} ) );
# clone
my $desc;
foreach ( keys %{$odesc} ) {
next
if ( $_ eq "scope"
|| $_ eq "format"
|| $_ eq "factor"
|| $_ eq "scale" );
$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) );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr $_: value replaced by language specific version ($l)"
);
}
# 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) );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr $_: value replaced by base language specific version ($1)"
);
}
}
# 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
my $value_num =
ref( $desc->{value_num} ) eq "ARRAY"
? $desc->{value_num}[0]
: ( defined( $desc->{value_num} ) ? $desc->{value_num} : undef );
if ( defined($value_num) ) {
foreach ( 'suffix', 'symbol', 'txt', 'txt_pl', 'txt_long',
'txt_long_pl' )
{
if ( ref( $desc->{$_} ) eq "ARRAY"
&& $desc->{$_}[$value_num] )
{
my $v = $desc->{$_}[$value_num];
delete $desc->{$_};
$desc->{$_} = $v;
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr $_: value replaced based on ARRAY found using value from 'value_num'"
);
}
}
}
##########
# template support
#
# add metric name to suffix and txt
if ( $desc->{suffix}
&& $desc->{scale_txt_m} )
{
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix value rewritten by adding value from 'scale_txt_m'"
);
$desc->{suffix} = $desc->{scale_txt_m} . $desc->{suffix};
}
if ( $desc->{txt}
&& $desc->{scale_txt_long_m} )
{
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt value rewritten by adding value from 'scale_txt_long_m'"
);
$desc->{txt} = $desc->{scale_txt_long_m} . lc( $desc->{txt} );
}
# add time information to suffix and txt
if ( $desc->{suffix}
&& $desc->{scale_txt_t} )
{
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_t};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix value rewritten by adding value from 'scale_txt_t'"
);
}
if ( $desc->{txt}
&& $desc->{scale_txt_long_pl_t} )
{
$desc->{txt_pl} = $desc->{txt} . lc( $desc->{scale_txt_long_pl_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_pl value rewritten by adding value from 'scale_txt_long_pl_t'"
);
}
if ( $desc->{txt}
&& $desc->{scale_txt_long_t} )
{
$desc->{txt} = $desc->{txt} . lc( $desc->{scale_txt_long_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt value rewritten by adding value from 'scale_txt_long_t'"
);
}
# add square information to suffix and txt
# if no separate suffix_sq and txt_sq was found
if ( !$desc->{suffix_sq}
&& $desc->{scale_txt_sq} )
{
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_sq};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix value rewritten by adding value from 'scale_txt_sq'"
);
}
if ( !$desc->{txt_sq}
&& $desc->{scale_txt_long_sq} )
{
$desc->{txt} = $desc->{scale_txt_long_sq} . lc( $desc->{txt} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt value rewritten by adding value from 'scale_txt_long_sq'"
);
}
# add cubic information to suffix and txt
# if no separate suffix_cu and txt_cu was found
if ( !$desc->{suffix_cu}
&& $desc->{scale_txt_cu} )
{
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_cu};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix value rewritten by adding value from 'scale_txt_cu'"
);
}
if ( !$desc->{txt_cu}
&& $desc->{scale_txt_long_cu} )
{
$desc->{txt} = $desc->{scale_txt_long_cu} . lc( $desc->{txt} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt value rewritten by adding value from 'scale_txt_long_cu'"
);
}
# add time information to suffix and txt
# if no separate suffix_t and txt_t was found
if ( !$desc->{suffix_t}
&& $desc->{scale_txt_t} )
{
$desc->{suffix} = $desc->{suffix} . $desc->{scale_txt_t};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix value rewritten by adding value from 'scale_txt_t'"
);
}
if ( !$desc->{txt_t}
&& $desc->{scale_txt_long_pl_t} )
{
$desc->{txt_pl} = $desc->{txt} . lc( $desc->{scale_txt_long_pl_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_pl value rewritten by adding value from 'scale_txt_long_pl_t'"
);
}
if ( !$desc->{txt_t}
&& $desc->{scale_txt_long_t} )
{
$desc->{txt} = $desc->{txt} . lc( $desc->{scale_txt_long_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt value rewritten by adding value from 'scale_txt_long_t'"
);
}
# add metric name to suffix_sq
if ( $desc->{suffix_sq}
&& $desc->{scale_txt_m_sq}
&& $desc->{suffix_sq} !~ /$desc->{scale_txt_m_sq}/ )
{
$desc->{suffix_sq} = $desc->{scale_txt_m_sq} . $desc->{suffix_sq};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_sq value rewritten by adding value from 'scale_txt_m_sq'"
);
}
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_m_sq} )
{
$desc->{txt_sq} = $desc->{scale_txt_long_m_sq} . lc( $desc->{txt_sq} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_sq value rewritten by adding value from 'scale_txt_long_m_sq'"
);
}
# add square information to suffix_sq
if ( $desc->{suffix_sq}
&& $desc->{scale_txt_sq} )
{
$desc->{suffix_sq} = $desc->{suffix_sq} . $desc->{scale_txt_sq};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_sq value rewritten by adding value from 'scale_txt_sq'"
);
}
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_sq} )
{
$desc->{txt_sq} = $desc->{scale_txt_long_sq} . lc( $desc->{txt_sq} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_sq value rewritten by adding value from 'scale_txt_long_sq'"
);
}
# add time information to suffix_sq
if ( $desc->{suffix_sq}
&& $desc->{scale_txt_t} )
{
$desc->{suffix_sq} = $desc->{suffix_sq} . $desc->{scale_txt_t};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_sq value rewritten by adding value from 'scale_txt_t'"
);
}
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_pl_t} )
{
$desc->{txt_pl_sq} =
$desc->{txt_sq} . lc( $desc->{scale_txt_long_pl_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_pl_sq value rewritten by adding value from 'scale_txt_long_pl_t'"
);
}
if ( $desc->{txt_sq}
&& $desc->{scale_txt_long_t} )
{
$desc->{txt_sq} = $desc->{txt_sq} . lc( $desc->{scale_txt_long_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_sq value rewritten by adding value from 'scale_txt_long_t'"
);
}
# add metric name to suffix_cu
if ( $desc->{suffix_cu}
&& $desc->{scale_txt_m_cu} )
{
$desc->{suffix_cu} = $desc->{scale_txt_m_cu} . $desc->{suffix_cu};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_cu value rewritten by adding value from 'scale_txt_m_cu'"
);
}
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_m_cu} )
{
$desc->{txt_cu} = $desc->{scale_txt_long_m_cu} . lc( $desc->{txt_cu} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_cu value rewritten by adding value from 'scale_txt_long_m_cu'"
);
}
# add cubic information to suffix_cu
if ( $desc->{suffix_cu}
&& $desc->{scale_txt_cu} )
{
$desc->{suffix_cu} = $desc->{suffix_cu} . $desc->{scale_txt_cu};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_cu value rewritten by adding value from 'scale_txt_cu'"
);
}
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_cu} )
{
$desc->{txt_cu} = $desc->{scale_txt_long_cu} . lc( $desc->{txt_cu} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_cu value rewritten by adding value from 'scale_txt_long_cu'"
);
}
# add time information to suffix_cu
if ( $desc->{suffix_cu}
&& $desc->{scale_txt_t} )
{
$desc->{suffix_cu} = $desc->{suffix_cu} . $desc->{scale_txt_t};
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr suffix_cu value rewritten by adding value from 'scale_txt_t'"
);
}
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_pl_t} )
{
$desc->{txt_pl_cu} =
$desc->{txt_cu} . lc( $desc->{scale_txt_long_pl_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_pl_cu value rewritten by adding value from 'scale_txt_long_pl_t'"
);
}
if ( $desc->{txt_cu}
&& $desc->{scale_txt_long_t} )
{
$desc->{txt_cu} = $desc->{txt_cu} . lc( $desc->{scale_txt_long_t} );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: RAttr txt_cu value rewritten by adding value from 'scale_txt_long_t'"
);
}
###############################
# generate short text string
#
# find text template
$txt = '%value%' . chr(0x00A0) . '%suffix%' if ( !$desc->{symbol} );
$txt = '%value%' . chr(0x202F) . '%symbol%' if ( $desc->{symbol} );
$txt = '%value%' . chr(0x202F) . '%symbol%' if ( $desc->{symbol} );
$txt = $desc->{tmpl} if ( $desc->{tmpl} );
$txt = '%txt%'
if ( defined( $desc->{txt} ) && !looks_like_number($value) );
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: detected txt template: $txt" );
# 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} } ) {
next if ( ref( $r->{$reading}{$k} ) );
$txt =~ s/%$k%/$r->{$reading}{$k}/g;
Unit_Log3( $device, $reading, $odesc, 10,
"replaceTemplate $device $reading: RAttr txt: replacing '%$k%'"
);
}
}
# Replace all %text% placeholders with text value found
# in matching reading desc hash keys
foreach my $k ( keys %{$desc} ) {
my $vdm = $desc->{$k};
next if ( ref($vdm) );
$vdm = UConv::decimal_mark( $vdm )
if ( defined( $desc->{decimal_mark} ) );
$txt =~ s/%$k%/$vdm/g;
Unit_Log3( $device, $reading, $odesc, 10,
"replaceTemplate $device $reading: RAttr txt: replacing '%$k%'" );
}
return ($txt) unless (wantarray);
###############################
# generate long text string in plural
#
# find text template
if ( looks_like_number($value)
&& ( $value eq "0" || $value > 1 || $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 || $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) {
Unit_Log3( $device, $reading, $odesc, 9,
"replaceTemplate $device $reading: detected txt_long template: $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} } ) {
next if ( ref( $r->{$reading}{$k} ) );
$txt_long =~ s/%$k%/$r->{$reading}{$k}/g;
Unit_Log3( $device, $reading, $odesc, 10,
"replaceTemplate $device $reading: RAttr txt_long: replacing '%$k%'"
);
}
}
# Replace all %text% placeholders with text value found
# in matching reading desc hash keys
foreach my $k ( keys %{$desc} ) {
my $vdm = $desc->{$k};
next if ( ref($vdm) );
$vdm = UConv::decimal_mark( $vdm )
if ( defined( $desc->{decimal_mark} ) );
$txt_long =~ s/%$k%/$vdm/g;
Unit_Log3( $device, $reading, $odesc, 10,
"replaceTemplate $device $reading: RAttr txt_long: replacing '%$k%'"
);
}
}
my $unit =
$desc->{symbol} ? $desc->{symbol}
: (
$desc->{suffix} ? $desc->{suffix}
: ( $desc->{txt} ? $desc->{txt} : undef )
);
$txt = Encode::encode_utf8($txt) if ( defined($txt) );
$txt_long = Encode::encode_utf8($txt_long) if ( defined($txt_long) );
$unit = Encode::encode_utf8($unit) if ( defined($unit) );
return ( $txt, $txt_long, $unit );
}
sub Unit_verifyValueNumber($$) {
my ( $value, $scope ) = @_;
my $log;
my $value_num;
my $verified = 0;
foreach ( keys %{$scope} ) {
next
if ( $_ !~ /^(regex|eq|minValue|maxValue|lt|gt|le|ge)$/ );
$verified = 1;
if ( !looks_like_number($value) ) {
if ( $_ eq 'regex' ) {
$log .= " '$value' does not match regex $scope->{$_}."
if ( $value !~ /$scope->{$_}/ );
$value_num = $scope->{value_num}
if ( defined( $scope->{value_num} ) );
}
elsif ( $_ eq 'eq' ) {
if ( $value ne $scope->{eq} ) {
$log .= " $value is not equal to $scope->{eq}.";
$value = $scope->{eq}
if ( !$scope->{keep} || $scope->{strict} );
}
$value_num = $scope->{value_num}
if ( defined( $scope->{value_num} ) );
}
elsif ( !$scope->{regex} && !defined( $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} ) );
last;
}
}
elsif ( $_ eq 'minValue' ) {
if ( $value < $scope->{$_} ) {
$log .= " $value is lesser than $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'maxValue' ) {
if ( $value > $scope->{$_} ) {
$log .= " $value is greater than $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'lt' ) {
if ( $value >= $scope->{$_} ) {
$log .= " $value is not $_ $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'le' ) {
if ( $value > $scope->{$_} ) {
$log .= " $value is not $_ $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'gt' ) {
if ( $value <= $scope->{$_} ) {
$log .= " $value is not $_ $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'ge' ) {
if ( $value < $scope->{$_} ) {
$log .= " $value is not $_ $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
}
elsif ( $_ eq 'eq' ) {
if ( $value ne $scope->{$_} ) {
$log .= " $value is not equal to $scope->{$_}.";
$value = $scope->{$_}
if ( !$scope->{keep} || $scope->{strict} );
}
else {
$log = undef;
last;
}
}
}
return ( $verified, $value, $value_num, $log );
}
# 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;
if ( !defined($value)
|| ref($value)
|| AttrVal( $device, "showUnits", 1 ) eq "0" )
{
Unit_Log3( $device, $reading, $desc, 10,
"formatValue $device $reading: explicit return of original value" );
return $value;
}
$desc = readingsDesc( $device, $reading )
if ( !$desc || !ref($desc) );
if ( !$format && ( !$desc || ref($desc) ne 'HASH' )
|| keys %{$desc} < 1 )
{
Unit_Log3( $device, $reading, $desc, 10,
"formatValue $device $reading: "
. "no readings description found, returning original value" );
return $value;
}
# 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 ( ref( $desc->{factor} ) || !looks_like_number( $desc->{factor} ) ) {
Unit_Log3( $device, $reading, $desc, 5,
"formatValue $device $reading: ERROR: "
. "RAttr factor is not numeric" );
}
elsif ( looks_like_number($value) ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: "
. "multiply original numeric value with factor "
. $desc->{factor} );
$value *= $desc->{factor};
}
else {
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: 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;
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: RAttr scope: "
. "value replaced by language specific version ($slang)" );
}
elsif ( ref($scope) eq "HASH" && defined( $scope->{$slang_base} ) ) {
my $v;
$v = $scope->{$slang_base};
$scope = undef;
$scope = $v;
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: RAttr scope: "
. "value replaced by language specific version ($slang_base)" );
}
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope dump:\n" . Dumper($scope) )
if ($scope);
################################
# scope:
# Check for value to be in correct scope
#
# Use user defined subroutine
if ( ref($scope) eq 'CODE' && &$scope ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "running external subroutine $scope()" );
( $value, $value_num ) = $scope->($value);
}
# scope was defined as HASH
elsif ( ref($scope) eq 'HASH' ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "verifying '$value' based on HASH structure" );
my ( $log, $verified );
( $verified, $value, $value_num, $log ) =
Unit_verifyValueNumber( $value, $scope );
Unit_Log3( $device, $reading, $desc, 4,
"formatValue $device $reading: scope: WARNING -$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' ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "verifying value '$value' based on ARRAY structure" );
# 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] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' ($lang)"
);
$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] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' ($lang_base)"
);
$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] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' (en / default language)"
);
$value = $desc->{txt}{en}[$value];
}
# if there is no language defined at all
elsif ( ref( $desc->{txt} ) eq "ARRAY"
&& defined( $desc->{txt}[$value] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by value from 'txt'" );
$value = $desc->{txt}[$value];
}
# if there is no language defined at all
elsif ( !ref( $desc->{txt} )
&& defined( $desc->{txt} ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by plane value from 'txt'" );
$value = $desc->{txt};
}
# if there is no txt definition at all
# arrays are unbalanced
else {
Unit_Log3( $device, $reading, $desc, 7,
"formatValue $device $reading: scope: ERROR - "
. "unbalanced number of items in arrays" );
}
}
else {
my $i = 0;
foreach ( @{$scope} ) {
#TODO
if ( !ref($_) && $value =~ /$_/gmi ) {
$value_num = $i;
# 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}[$i] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' ($lang)"
);
$value = $desc->{txt}{$lang}[$i];
}
# also try base language
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{$lang_base} ) eq "ARRAY"
&& defined( $desc->{txt}{$lang_base}[$i] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' ($lang_base)"
);
$value = $desc->{txt}{$lang_base}[$i];
}
# fallback to english
elsif (ref( $desc->{txt} ) eq "HASH"
&& ref( $desc->{txt}{en} ) eq "ARRAY"
&& defined( $desc->{txt}{en}[$i] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by language specific value from 'txt' (en / default language)"
);
$value = $desc->{txt}{en}[$i];
}
# if there is no language defined at all
elsif ( ref( $desc->{txt} ) eq "ARRAY"
&& defined( $desc->{txt}[$i] ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by value from 'txt'" );
$value = $desc->{txt}[$i];
}
# if there is no language defined at all
elsif ( !ref( $desc->{txt} )
&& defined( $desc->{txt} ) )
{
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: rattr value: "
. "replaced by plane value from 'txt'" );
$value = $desc->{txt};
}
# if there is no txt definition at all
# arrays are unbalanced
else {
$value = $1 if ( defined($1) );
if ( !defined($1) ) {
Unit_Log3( $device, $reading, $desc, 7,
"formatValue device $reading: scope: ERROR - "
. "missing txt value or regex output (i=$i)"
);
}
}
last;
}
#TODO
elsif ( ref($_) eq "HASH" ) {
if ( !looks_like_number($value) && $_->{regex} ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "searching numeric value for string '$value' (i=$i)"
);
if ( $value =~ /$_->{regex}/ ) {
if ( defined( $_->{value_num} ) ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "static numeric value found in HASH" );
$value_num->[0] = $i;
$value_num->[1] = $_->{value_num};
}
else {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "assuming indirect numeric value from ARRAY index number"
);
$value_num = $i;
}
last;
}
}
#TODO: wenn nummer out of scope, dann muss ein maximalwert gesetzt sein
else {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "verifying numeric value '$value' (i=$i)" );
my ( $verified, $tval, $tval_num, $log ) =
Unit_verifyValueNumber( $value, $_ );
if ($log) {
Unit_Log3( $device, $reading, $desc, 4,
"formatValue $device $reading: scope: WARNING -$log"
);
}
elsif ( $verified && !$log ) {
if ( defined($tval_num) ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "static numeric value found in HASH" );
$value_num->[0] = $i;
$value_num->[1] = $tval_num;
}
else {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "assuming indirect numeric value from ARRAY index number"
);
$value_num = $i;
}
$value = $tval;
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) ) {
Unit_Log3( $device, $reading, $desc, 9,
"formatValue $device $reading: scope: "
. "'$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 "" ) {
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: scope: WARNING - "
. "'$value' does not match regex $scope" );
}
# format
#
if ( $format && !looks_like_number($value) ) {
Unit_Log3( $device, $reading, $desc, 8,
"formatValue $device $reading: format: ERROR - $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 = $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' ) {
Unit_Log3( $device, $reading, $desc, 8,
"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, $unit ) =
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, $unit ) 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 ) = @_;
$device = "" unless ( defined($device) );
my $desc = getCombinedKeyValAttr( $device, "readingsDesc", $reading );
my $rtype;
if ( $desc->{rtype} ) {
$rtype = $desc->{rtype};
}
else {
$rtype = rname2rtype( $device, $reading );
$desc->{rtype} = $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" } ) );
}
}
if ( $desc->{scale_t} ) {
my $ref = $desc->{scale_t};
foreach my $k ( keys %{ $scales_t->{$ref} } ) {
$desc->{$k} = $scales_t->{$ref}{$k}
if ( !defined( $desc->{$k} ) );
}
}
$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} ) );
}
}
Unit_Log3( $device, $reading, $desc, 10,
"readingsDesc $device $reading:\n" . Dumper($desc) );
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 ( $name && $defs{$name} );
my $m = $modules{ $d->{TYPE} } if ( $d && $d->{TYPE} );
my $g = $defs{"global"};
# join hashes until 3rd level
my $desc;
if ( $m
&& $attribute
&& $m->{$attribute}
&& ref( $m->{$attribute} ) eq "HASH" )
{
Log3( $name, 5,
"getCombinedKeyValAttr $name $reading: including HASH from module X_Initialize() function"
);
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
&& $attribute
&& $g->{$attribute}
&& ref( $g->{$attribute} ) eq "HASH" )
{
Log3( $name, 5,
"getCombinedKeyValAttr $name $reading: including HASH from global attribute $attribute"
);
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
&& $attribute
&& $d->{$attribute}
&& ref( $d->{$attribute} ) eq "HASH" )
{
Log3( $name, 5,
"getCombinedKeyValAttr $name $reading: including HASH from device attribute $attribute"
);
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::Deepcopy = 1;
$Data::Dumper::Sortkeys = 1;
my $txt = Dumper( $d->{$attribute} );
$Data::Dumper::Terse = 0;
$Data::Dumper::Deepcopy = 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|min\d+m|max\d+m|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, $symbol ) =
formatReading( $name, $reading, "" );
if ( defined($txt) && defined($reading) && defined($val) ) {
$txt =~ s/[\s\u202F\u00A0]*$val[\s\u202F\u00A0]*//;
$value = $val;
if ( !looks_like_number($val) && defined($val_num) ) {
if ( ref($val_num) eq "ARRAY" ) {
$value = $val_num->[1];
}
else {
$value = $val_num;
}
}
$unit = defined($symbol) ? $symbol : $txt;
}
}
# general event handling
if ( !defined($value)
&& $event =~
m/^(.+): +[\D]*(\d+\.?\d*)[\s\u202F\u00A0]*[\[\{\(]?[\s\u202F\u00A0]*([\w\°\%\^\/\\]*).*/
&& defined($1)
&& defined($2) )
{
$reading = $1;
$value = ReadingsNum( $name, $1, $2 );
$unit = defined($3) ? $3 : "";
}
if ( !defined($value) || !looks_like_number($value) ) {
Unit_Log3( $name, $reading, undef, 10,
"Unit_DbLog_split $name: Ignoring event $event: value $value does not look like a number"
) if ( defined($value) );
return undef;
}
Unit_Log3( $name, $reading, undef, 9,
"Unit_DbLog_split $name: Splitting event $event > reading=$reading value=$value unit=$unit"
);
return ( $reading, $value, $unit );
}
################################################
# the new Log with integrated loglevel checking for readings
sub Unit_Log3($$$$$) {
my ( $dev, $rname, $desc, $loglevel, $text ) = @_;
$dev = $dev->{NAME} if ( defined($dev) && ref($dev) eq "HASH" );
$desc = readingsDesc( $dev, $rname )
if ( !$desc || !ref($desc) );
my $rloglevel = $loglevel;
if ( $desc
&& ref($desc) eq "HASH"
&& defined( my $rlevel = $desc->{verbose} ) )
{
return if ( $loglevel > $rlevel );
$rloglevel = $rloglevel - 9;
}
return Log3( $dev, $rloglevel, "RType: " . $text );
}
################################################################
#
# User commands
#
################################################################
# command: setreadingdesc
my %setreadingdeschash = (
Fn => "CommandSetReadingDesc",
Hlp =>
" [noCheck] =[|?],set reading rtype information for ",
);
$cmds{setreadingdesc} = \%setreadingdeschash;
sub CommandSetReadingDesc($@) {
my ( $cl, $def ) = @_;
my $attribute = "readingsDesc";
my $namedef =
"where is a single device name, a list separated by comma (,) or a regexp. See the devspec section in the commandref.html for details.\n"
. " 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 [noCheck] =[|?]\n$namedef"
if ( $a->[0] eq "?" || $a->[1] eq "?" || !%{$h} );
my @rets;
my $last;
foreach my $name ( devspec2array( $a->[0], $cl ) ) {
unless ( IsDevice($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 =>
" [],delete key for ",
);
$cmds{deletereadingdesc} = \%deletereadingdeschash;
sub CommandDeleteReadingDesc($@) {
my ( $cl, $def ) = @_;
my $attribute = "readingsDesc";
my $namedef =
"where is a single device name, a list separated by comma (,) or a regexp. See the devspec section in the commandref.html for details.\n"
. " and 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 []\n$namedef"
if ( $a->[0] eq "?" || $a->[1] eq "?" );
my @rets;
my $last;
foreach my $name ( devspec2array( $a->[0], $cl ) ) {
unless ( IsDevice($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;
=pod
=encoding utf8
=for :application/json;q=META.json Unit.pm
{
"author": [
"Julian Pawlowski "
],
"x_fhem_maintainer": [
"loredo"
],
"x_fhem_maintainer_github": [
"jpawlowski"
],
"keywords": [
"RType",
"Unit"
]
}
=end :application/json;q=META.json
=cut