# $Id$ # # All rights reserved # # FHEM Forum : https://forum.fhem.de/index.php/board,26.0.html # # This code is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the textfile GPL.txt and important notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ################################################################ package FHEM::OW2S0SMSGUARD; ## no critic 'package' use strict; use warnings; use Time::HiRes qw(gettimeofday sleep); use DevIo; use Scalar::Util qw(looks_like_number); use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt BEGIN { # Import from main:: GP_Import( qw( attr AttrNum AttrVal CommandAttr defs deviceEvents DevIo_CloseDev DevIo_OpenDev DevIo_SimpleRead Dispatch DoTrigger init_done IsDisabled InternalTimer Log3 modules ReadingsVal readingsSingleUpdate readingsBulkUpdate readingsBeginUpdate readingsEndUpdate readingFnAttributes RemoveInternalTimer setReadingsVal TimeNow ) ); # Export to main GP_Export( qw(Initialize) ); } my $hasmeta = 0; # ältere Installationen haben noch kein Meta.pm if (-e $attr{global}{modpath}.'/FHEM/Meta.pm') { $hasmeta = 1; require FHEM::Meta; } ##################################### sub Initialize { my $hash = shift; $hash->{Clients} = ':OW2S0SMSGUARD:'; $hash->{MatchList} = { '1:OW2S0SMSGUARD' => '^OW.*' }; $hash->{ReadFn} = \&ReadFn; $hash->{DefFn} = \&DefineFn; $hash->{UndefFn} = \&UndefFn; $hash->{NotifyFn} = \&NotifyFn; $hash->{GetFn} = \&GetFn; $hash->{SetFn} = \&SetFn; $hash->{AttrFn} = \&AttrFn; $hash->{ParseFn} = \&ParseFn; $hash->{AttrList} = 'interval disable:0,1 mapOWIDs useSubDevices:0,1 ' .'model:master,unknown,DS2401,DS1820,DS18B20,DS1822 '.$readingFnAttributes; return FHEM::Meta::InitMod( __FILE__, $hash ) if ($hasmeta); return; } ##################################### sub DefineFn { my $msg = 'wrong syntax: define OW2S0SMSGUARD devicename'; my $hash = shift; my $def = shift // return $msg; my ($name, undef, $dev, $interval) = split(m{ \s+ }xms, $def, 4); $dev // return $msg; DevIo_CloseDev($hash); my $addr = ( $dev !~ m/\@/x && $dev !~ m/:/x && $dev !~ m/\./x) ? $dev : 'master'; if ($addr eq 'master') { $hash->{DeviceName} = $dev; $interval //= 30; $hash->{INTERVAL} = $interval; $hash->{OWDEVICES} = 0; CommandAttr(undef, "$name model master") if (!exists($attr{$name}{model})); } else { if (exists($modules{OW2S0SMSGUARD}{defptr}{$addr}) && $modules{OW2S0SMSGUARD}{defptr}{$addr}->{NAME} ne $name) { return "$name, a OW2S0SMSGUARD device with address $addr is already defined as ".$modules{OW2S0SMSGUARD}{defptr}{$addr}->{NAME}; } } $hash->{STATE} = 'defined'; $hash->{NOTIFYDEV} = 'global'; $hash->{addr} = $addr; $hash->{SVN} = (qw($Id:BETA $))[2]; $modules{OW2S0SMSGUARD}{defptr}{$addr} = $hash; return $@ if ($hasmeta && !FHEM::Meta::SetInternals($hash)); return; } ##################################### sub UndefFn { my $hash = shift; delete $modules{OW2S0SMSGUARD}{defptr}{$hash->{addr}}; DevIo_CloseDev($hash) if ($hash->{addr} eq 'master'); return; } ##################################### sub NotifyFn { # $hash is my hash, $dhash is the hash of the changed device my $hash = shift; my $dhash = shift; my $name = $hash->{NAME}; my $events = deviceEvents($dhash, 0); my $ev_str = join('|', @{$events}); if (($dhash->{NAME} eq 'global') && ((index($ev_str, 'INITIALIZED') > -1) || (index($ev_str, 'REREADCFG') > -1))) { return if ($hash->{addr} ne 'master'); $attr{$name}{interval} = $hash->{INTERVAL} if (!exists($attr{$name}{interval})); $hash->{INTERVAL} = $attr{$name}{interval}; if (index($ev_str, 'INITIALIZED') > -1) { # nur bei FHEM Neustart eventuelle DS2401 vorbesetzen my @ds2401 = split(' ', ReadingsVal($name, '.ds2401' ,'')); foreach my $dev (@ds2401) { $hash->{helper}{100}{$dev}{name} = mapNames($hash, 100, $dev); } foreach my $dev (@ds2401) { setReadingsVal($hash, $hash->{helper}{100}{$dev}{name}, 'unkown', TimeNow()); Log3($name, 5, "$name, restore hash for $dev"); } } RemoveInternalTimer($hash); DevIo_CloseDev($hash); DevIo_OpenDev($hash, 1, \&DoInit) if ($hash->{INTERVAL} && !IsDisabled($name)); readingsSingleUpdate($hash, 'state', 'disabled', 1) if (!$hash->{INTERVAL} || !IsDisabled($name)); } return; } ##################################### sub AttrFn { my ($cmd, $name, $attrName, $attrVal) = @_; my $hash = $defs{$name}; return if ($hash->{addr} ne 'master'); if ($cmd eq 'del') { RemoveInternalTimer($hash); $hash->{INTERVAL} = 0 if ($attrName eq 'interval'); InternalTimer(gettimeofday()+1, "FHEM::OW2S0SMSGUARD::GetUpdate", $hash, 0) if ($attrName eq 'disable'); } if ($cmd eq 'set') { if ($attrName eq 'interval') { return "invalid value " if (int($attrVal) < 0); $hash->{INTERVAL} = int($attrVal); InternalTimer(gettimeofday()+1, "FHEM::OW2S0SMSGUARD::GetUpdate", $hash, 0) if ($hash->{INTERVAL}); readingsSingleUpdate($hash, 'state', 'disabled', 1) if (!$hash->{INTERVAL}); } if ($attrName eq 'disable') { if (int($attrVal) == 1) { DevIo_CloseDev($hash); readingsSingleUpdate($hash, 'state', 'disabled', 1); $hash->{INTERVAL} = 0; RemoveInternalTimer($hash); } if (int($attrVal) == 0) { $hash->{INTERVAL} = AttrNum($name, 'interval', 30); InternalTimer(gettimeofday()+1, "FHEM::OW2S0SMSGUARD::GetUpdate", $hash, 0) if ($hash->{INTERVAL}); DevIo_CloseDev($hash); DevIo_OpenDev($hash, 1, \&DoInit) if ($hash->{INTERVAL}); #$hash, $reopen, $initfn, $callback } } } return; } ##################################### sub DoInit { my $hash = shift; Log3($hash, 5, "$hash->{NAME}, DoInit"); if ($hash->{INTERVAL}) { RemoveInternalTimer($hash); SimpleWrite($hash, "\$L+\n\$?"); InternalTimer(gettimeofday()+1, "FHEM::OW2S0SMSGUARD::GetUpdate", $hash, 0); } return; } ##################################### sub GetUpdate { my $hash = shift; my $name = $hash->{NAME}; Log3($name, 5, "$name, GetUpdate"); return if (IsDisabled($name) || !$hash->{INTERVAL} || ($hash->{addr} ne 'master')); InternalTimer(gettimeofday()+$hash->{INTERVAL}, "FHEM::OW2S0SMSGUARD::GetUpdate", $hash, 0); SimpleWrite($hash, '$?'); return; } ##################################### sub read_OW { my $h = shift; my $hash = $h->{h}; my $num = $h->{n}; SimpleWrite($hash, '$'.$num); Log3($hash, 5, "$hash->{NAME}, read_OW : $num"); return; } ##################################### sub SetFn { my $hash = shift; return if ($hash->{addr} ne 'master'); my $name = shift; my $cmd = shift // '?'; if ($cmd eq 'reset') { DevIo_CloseDev($hash); return DevIo_OpenDev($hash, 1, \&DoInit); } return SimpleWrite($hash, '$rez') if ($cmd eq 'S0-reset'); return "Unknown argument $cmd, choose one of reset:noArg S0-reset:noArg"; } ##################################### sub GetFn { my $hash = shift; return if ($hash->{addr} ne 'master'); my $name = shift; my $cmd = shift // '?'; my $device = shift // 0; if ($cmd eq 'OWdevicelist') { my @devs; foreach my $dev ( sort keys %{$hash->{helper}} ) { next if (int($dev) == 100); push @devs, "$dev,$hash->{helper}{$dev}{typ},$hash->{helper}{$dev}{addr},$hash->{helper}{$dev}{name},$hash->{helper}{$dev}{time}"; } return formatOWList(@devs); } return "$name get with unknown argument $cmd, choose one of OWdevicelist:noArg"; } sub formatOWList { my (@devs) = @_; # Type | Address | Name | Time # -------+------------------+--------+-------------------- # DS1820 | 10D64CBF02080077 | Keller | 2021-01-30 08:44:55 # DS2401 | 018468411C0000BA | TestDS | 2021-01-30 08:44:55 # -------+------------------+--------+-------------------- return 'Sorry, no OW devices found !' if (!int(@devs)); my ($ow,$yw,$dw,$nw,$tw) = (1,6,8,5,5); # Startbreiten, bzw. Mindestbreite durch Überschrift foreach my $dev (@devs) { my ($o,$y,$d,$n,$t) = split(',', $dev); # die tatsächlichen Breiten aus den vorhandenen Werten ermitteln $ow = (length($o) > $ow) ? length($o) : $ow; $yw = (length($y) > $yw) ? length($y) : $yw; $dw = (length($d) > $dw) ? length($d) : $dw; $nw = (length($n) > $nw) ? length($n) : $nw; $tw = (length($t) > $tw) ? length($t) : $tw; } my $head = '# | Type' .(' ' x ($yw-4)) .' | Address'.(' ' x ($dw-7)) .' | Name' .(' ' x ($nw-4)) .' | Time' .(' ' x ($tw-4)); my $separator = ('-' x length($head)); while ( $head =~ m{\|}xg ) { # alle | Positionen durch + ersetzen substr $separator, (pos($head)-1), 1, '+'; } $head .= "\n".$separator."\n"; my $s; foreach my $dev (@devs) { ; my ($o,$y,$d,$n,$t) = split(',', $dev); $s .= $o . (' ' x ($ow - length($o))).' | '; $s .= $y . (' ' x ($yw - length($y))).' | '; $s .= $d . (' ' x ($dw - length($d))).' | '; $s .= $n . (' ' x ($nw - length($n))).' | '; $s .= $t . (' ' x ($tw - length($t))); $s .= "\n"; } return $head.$s.$separator; } ##################################### # called from the global loop, when the select for hash->{FD} reports data sub ReadFn { my $hash = shift; my $name = $hash->{NAME}; my $buf = DevIo_SimpleRead($hash); return '' if (!defined($buf)); my $raw = $hash->{PARTIAL}; Log3($name, 5, "$name, RAW: $raw / $buf"); $raw .= $buf; my $i = 0; while($raw =~ m/\n/x) { my $rmsg; ($rmsg,$raw) = split("\n", $raw, 2); $rmsg =~ s/[\r\$]//xg; if ($rmsg) { $i++; Log3($name, 4, "$name, read[$i] : $rmsg"); Parse($hash, $rmsg) if (index($rmsg, ';') != -1); } } $hash->{PARTIAL} = $raw; return; } ##################################### sub Parse { my $hash = shift; my $rmsg = shift // return; my $name = $hash->{NAME}; my $txt; $rmsg =~ s/ //g; my @data = split(';', $rmsg); if (int(@data) < 3) { $txt = 'message is too short ['.int(@data).']'; Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } return UpdateReadings($hash, $rmsg) if ($data[0] eq 'S0'); # Liste ist vollständig if (!looks_like_number($data[0])) { $txt = "first byte $data[0] is not a number !"; Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } my $ok = (defined($data[1]) && ($data[1] eq 'o')) ? 1 : 0; my $temp = ''; my $num = int($data[0]); if ($num > 63) { $txt = "invalid OW number $num"; Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } if (defined($data[2]) && (length($data[2]) == 16) && ( $data[2] =~ m{ [0-9A-F]+ }x )) { delete $hash->{helper}{$num}; my $model = int(substr($data[2],0,2)); $model = -1 if (!$model || ($model > 28) ); $hash->{helper}{$num}{state} = $ok; $hash->{helper}{$num}{time} = TimeNow(); $hash->{helper}{$num}{addr} = $data[2]; $hash->{helper}{$num}{fam} = $model; $hash->{'OW-Dev'.$num} = $hash->{helper}{$num}{addr}." => $ok"; $hash->{OWDEVICES} = ($num + 1); if ($num == 0) { for my $i (1..63) {delete $hash->{'OW-Dev'.$i} if (exists($hash->{'OW-Dev'.$i})); } } mapNames($hash, $num); $hash->{helper}{$num}{typ} = 'DS1822' if ($model == 22); $hash->{helper}{$num}{typ} = 'DS18B20' if ($model == 28); $hash->{helper}{$num}{typ} = 'DS1820' if ($model == 10); if ($model == 1) { $hash->{helper}{100}{$data[2]}{name} = $hash->{helper}{$num}{name}; $hash->{helper}{100}{$data[2]}{time} = TimeNow(); $hash->{helper}{100}{$data[2]}{busid} = $num; $hash->{helper}{$num}{typ} = 'DS2401'; $hash->{DS2401} .= $data[2].' '; } if (!defined($hash->{helper}{$num}{typ})) { $txt = "unknown OW type, address $data[2]"; Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } InternalTimer(gettimeofday()+1+($num*0.5), "FHEM::OW2S0SMSGUARD::read_OW", {h=>$hash, n=>$num}, 0) if ($ok && ($model > 9)); # 10 - 28 return; } if ($ok && defined($data[11]) && exists($hash->{helper}{$num})) { # das 10.Byte ist eine Checksumme für die serielle Übertragung my $crc; for my $i (2..10) { $crc += hex('0x'.$data[$i]); } $crc = $crc & 0xFF; if ($crc != hex('0x'.$data[11])) { $txt = "CRC error OW device $num : ".$data[11]. ' != '. sprintf('%02x', $crc); Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } shift @data; shift @data; my $model = $hash->{helper}{$num}{typ}; $hash->{helper}{$num}{raw} = join(' ' , @data); $temp = decodeTemperature($model, @data); if ($temp eq '') { $txt = "unable to decode data $hash->{helper}{$num}{raw} for model $model"; Log3($name, 3, "$name, $txt"); readingsSingleUpdate($hash, 'error', $txt, 1); return; } $hash->{helper}{$num}{value} = $temp; if (!AttrNum($name, 'useSubDevices', 0)) { readingsSingleUpdate($hash, $hash->{helper}{$num}{name}, $temp, 1); return; } readingsSingleUpdate($hash, $hash->{helper}{$num}{name}, $temp, 0); Dispatch($hash, "OW,$hash->{helper}{$num}{addr},$model,$temp,$num"); } return; } ##################################### sub decodeTemperature { my ($model, @data) = @_; my $temp = ''; if ($model eq 'DS1820') { $temp = (( hex('0x'.$data[1]) << 8) + hex('0x'.$data[0])) << 3; $temp = ($temp & 0xFFF0) +12 - hex('0x'.$data[6]) if ($data[7] eq '10'); } if (($model eq 'DS18B20') || ($model eq 'DS1822')) { $temp = (( hex('0x'.$data[1]) << 8) + hex('0x'.$data[0])); my $cfg = (hex('0x'.$data[4]) & 0x60); $temp = $temp << 3 if ($cfg == 0); $temp = $temp << 2 if ($cfg == 0x20); $temp = $temp << 1 if ($cfg == 0x40); } if ($temp) { $temp = $temp/16.0; $temp -= 4096 if (hex('0x'.$data[1]) > 127); $temp = sprintf('%.1f', $temp); } return $temp; } ##################################### sub ParseFn { my $shash = shift; my $msg = shift // return; Log3($shash, 5, "ParseFn, $msg"); my @arr = split(',', $msg); return if (!defined($arr[1]) || (length($arr[1]) != 16) || ($arr[1] !~ m{ [0-9A-F]+ }x )); my $model = $arr[2] // return; my $val = $arr[3] // return; if (!exists($modules{OW2S0SMSGUARD}{defptr}{$arr[1]})) { my $ac = (IsDisabled('autocreate')) ? 'disabled' : 'enabled' ; Log3($shash, 3, "$shash->{NAME}, got message for undefined device [$arr[1]] type $arr[2] autocreate is $ac"); return 'disable' if ($ac eq 'disabled'); return "UNDEFINED OW_$arr[1] OW2S0SMSGUARD $arr[1]"; } my $dhash = $modules{OW2S0SMSGUARD}{defptr}{$arr[1]}; my $dname = $dhash->{NAME} // return; Log3($dname, 4, "$dname, ParseFn $msg"); $dhash->{model} = $model; $dhash->{busid} = $arr[4] //= -1; readingsBeginUpdate($dhash); if (($model eq 'DS1820') || ($model eq 'DS18B20') || ($model eq 'DS1822') ) { readingsBulkUpdate($dhash, 'temperature', $val); readingsBulkUpdate($dhash, 'state', "T: $val °C"); } elsif ($model eq 'DS2401') { readingsBulkUpdate($dhash, 'state', $val); readingsBulkUpdate($dhash, 'presence', $val); } readingsEndUpdate($dhash,1); CommandAttr(undef, "$dname model $model") if (!exists($attr{$dname}{model})); return $dname; } ##################################### sub UpdateReadings { my $hash = shift; my @data = split(';', shift); my @arr; my $S0A = (looks_like_number($data[1])) ? int($data[1]) : ''; my $S0B = (looks_like_number($data[2])) ? int($data[2]) : ''; Log3($hash, 5, "$hash->{NAME}, UpdateReadings data : ".join(' ',@data)); readingsBeginUpdate($hash); readingsBulkUpdate($hash, 'A', $S0A) if ($S0A ne ''); readingsBulkUpdate($hash, 'B', $S0B) if ($S0B ne ''); readingsBulkUpdate($hash, 'state', "A: $S0A - B: $S0B") if (($S0A ne '') && ($S0B ne '')); readingsEndUpdate($hash, 1); if ($hash->{DS2401}) { readingsBeginUpdate($hash); Log3($hash, 4, "$hash->{NAME}, DS2401 Devices $hash->{DS2401}"); #readingsBulkUpdate($hash,'TEST',$hash->{DS2401}); foreach my $dev ( keys %{$hash->{helper}{100}} ) { $hash->{helper}{100}{$dev}{presence} = (index($hash->{DS2401}, $dev) != -1) ? 'present' : 'absent'; readingsBulkUpdate($hash, $hash->{helper}{100}{$dev}{name}, $hash->{helper}{100}{$dev}{presence}); push @arr, "OW,$dev,DS2401,$hash->{helper}{100}{$dev}{presence},$hash->{helper}{100}{$dev}{busid}"; } readingsBulkUpdate($hash, '.ds2401', OW_uniq($hash->{NAME}, '.ds2401', $hash->{DS2401})); delete $hash->{DS2401}; if (!AttrNum($hash->{NAME}, 'useSubDevices', 0)) { readingsEndUpdate($hash, 1); return; } readingsEndUpdate($hash, 0); foreach my $dev (@arr) { Dispatch($hash, $dev); } } return; } ##################################### sub mapNames { my $hash = shift; my $num = shift // 0; my $address = shift // ''; my $m = AttrVal($hash->{NAME}, 'mapOWIDs' , ''); $hash->{helper}{$num}{name} = $hash->{helper}{$num}{addr} if ($num != 100); return $address if (!$m); $m =~ s/ //g; my @names = split(',', $m); foreach my $n (@names) { my ($addr,$reading) = split('=' , $n); if (($num != 100) && ($addr eq $hash->{helper}{$num}{addr})) { $hash->{helper}{$num}{name} = $reading; Log3($hash, 4, "$hash->{NAME}, found name $reading for device [$num] $addr"); return; } return $reading if (($num == 100) && ($addr eq $address)); } return $address; } ##################################### sub SimpleWrite { my $hash = shift // return; my $msg = shift // return; my $name = $hash->{NAME}; Log3($name, 4, "$name, SimpleWrite: $msg"); $msg .= "\n"; $hash->{USBDev}->write($msg) if ($hash->{USBDev}); syswrite($hash->{TCPDev}, $msg) if ($hash->{TCPDev}); syswrite($hash->{DIODev}, $msg) if ($hash->{DIODev}); # Some linux installations are broken with 0.001, T01 returns no answer #select(undef, undef, undef, 0.01); sleep(0.01); return; } ##################################### sub OW_uniq { my @arr = split(' ', ReadingsVal(shift, shift, '')); my @vals = split(' ', shift); foreach (@vals) {push @arr, $_;} my @unique; my %h; foreach my $v (@arr) { if ( !$h{$v} ) { push @unique, $v; $h{$v} = 1; } } @arr = sort @unique; return join(' ', @arr); } ##################################### 1; __END__ =pod =over =encoding utf8 =item summary Module for SMS USB Guard =item summary_DE Modul für SMS USB Guard =begin html

OW2SMSGUARD.

FHEM Forum : 1Wire
Define
Set
  • reset IO device ( master only )

  • S0-reset reset of both S0 counters ( master only )

  • Get
  • list of found OW devices ( master only )

  • Attributes =end html =begin html_DE

    OW2S0SMSGUARD.

    1-wire USB Master von sms-guard.org FHEM Forum : 1Wire
    Define
    Set
  • reset IO Device ( nur Master Device )

  • S0-resets ( nur Master Device )
    setzt die beiden internen S0 Zähler ( A & B) auf 0 zurück

  • Get
  • OWdeviceList
    Liste der aktuell gefunden OW Geräte ( nur Master Device )

  • Attribute =end html_DE =for :application/json;q=META.json 00_OW2S0SMSGUARD.pm { "abstract": "Module for 2 S0 counter and 1-wire USB Master from SMS-Guard.org", "x_lang": { "de": { "abstract": "Modul für zwei S0 counter and 1-wire USB Master von SMS-Guard.org" } }, "keywords": [ "S0", "One Wire", "counter", "1W" ], "version": "2.0", "release_status": "stable", "author": [ "Wzut" ], "x_fhem_maintainer": [ "Wzut" ], "x_fhem_maintainer_github": [ ], "prereqs": { "runtime": { "requires": { "FHEM": 5.00918799, "GPUtils": 0, "Time::HiRes": 0 }, "recommends": { "FHEM::Meta": 0 }, "suggests": { } } } } =end :application/json;q=META.json