From 714a49b3996912611236911c51406b802a4afd66 Mon Sep 17 00:00:00 2001
From: herrmannj <>
Date: Tue, 1 Sep 2020 20:10:39 +0000
Subject: [PATCH] 10_SchellenbergHandle.pm: initial check-in
git-svn-id: https://svn.fhem.de/fhem/trunk@22710 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/10_SchellenbergHandle.pm | 266 +++++++++++++++++++++++++++++
1 file changed, 266 insertions(+)
create mode 100644 fhem/FHEM/10_SchellenbergHandle.pm
diff --git a/fhem/FHEM/10_SchellenbergHandle.pm b/fhem/FHEM/10_SchellenbergHandle.pm
new file mode 100644
index 000000000..eb39c76fe
--- /dev/null
+++ b/fhem/FHEM/10_SchellenbergHandle.pm
@@ -0,0 +1,266 @@
+# $Id$
+###############################################################################
+#
+# This file is part of fhem.
+#
+# Fhem 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.
+#
+# Fhem is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with fhem. If not, see .
+#
+#
+###############################################################################
+
+# Thanks to hypfer for doing the basic research
+
+package main;
+
+use 5.018;
+use feature qw( lexical_subs );
+
+use strict;
+use warnings;
+use utf8;
+use Fcntl qw( :DEFAULT );
+
+no warnings qw( experimental::lexical_subs );
+
+sub SchellenbergHandle_Initialize {
+ my ($hash) = @_;
+
+ $hash->{'DefFn'} = 'SchellenbergHandle_Define';
+ $hash->{'UndefFn'} = 'SchellenbergHandle_Undef';
+ #$hash->{'DeleteFn'} = 'Schellenberg_Delete';
+ #$hash->{'SetFn'} = 'SchellenbergHandle_Set';
+ #$hash->{'ReadFn'} = "Schellenberg_Read";
+ #$hash->{'ReadyFn'} = "Schellenberg_Ready";
+ $hash->{'ParseFn'} = "SchellenbergHandle_Parse";
+ $hash->{'Match'} = '^ss[[:xdigit:]]{1}4[[:xdigit:]]{16}';
+ $hash->{'AttrList'} = $readingFnAttributes;
+
+ return;
+};
+
+sub SchellenbergHandle_Define {
+ my ($hash, $def) = @_;
+ my ($name, $type, $id) = split /\s/, $def, 3;
+
+ my $cvsid = '$Id$';
+ $cvsid =~ s/^.*pm\s//;
+ $cvsid =~ s/Z\s\S+\s\$$/ UTC/;
+ $hash->{'SVN'} = $cvsid;
+
+ # id valid?
+ return 'invalid id' if ($id !~ m/^([[:xdigit:]]{6})$/);
+
+ # id exists AND device exists?
+ return 'handle already defined' if (exists($modules{'SchellenbergHandle'}{'defptr'}{$id}));
+
+ # set id to defptr
+ $hash->{'ID'} = $id;
+ $modules{'SchellenbergHandle'}{'defptr'}{$id} = $hash;
+ InternalTimer(0, \&SchellenbergHandle_Run, $hash);
+ return;
+};
+
+sub SchellenbergHandle_Undef {
+ my ($hash, $name) = @_;
+ # delete defptr
+ delete $modules{'SchellenbergHandle'}{'defptr'}{$hash->{'ID'}};
+ return;
+};
+
+# modul, all readings and attribute are loaded
+sub SchellenbergHandle_Run {
+ my ($hash) = @_;
+ return;
+};
+
+sub SchellenbergHandle_Set {
+ my ($hash, $name, $cmd, @args) = @_;
+
+ return "Unknown argument $cmd, choose one of none" if ($cmd eq '?');
+};
+
+# not used atm, rolling-code is synchronized after each startup
+sub SchellenbergHandle_SetPersistentData {
+ my ($hash, $mc, $close) = @_;
+
+ my $filename = File::Spec->catfile(Logdir(), "$hash->{'FUUID'}.data");
+ if (sysopen(my $fh, $filename, O_WRONLY|O_CREAT|O_TRUNC|O_SYNC)) {
+ binmode $fh, ':encoding(UTF-8):crlf';
+ say $fh sprintf('# This file is automatically generated by %s; do not modify.', $hash->{'NAME'});
+ # SBWH, version, gmt-timestamp, rolling-code, 0: intermediate, 1: clean shutdown LINE-END
+ say $fh sprintf('SBWH,1,%s,%s,%s', time(), 1234, 0);
+ close($fh);
+ } else {
+ say "error $!";
+ };
+};
+
+# not used atm, rolling-code is synchronized after each startup
+sub SchellenbergHandle_GetPersistentData {
+ my ($hash) = @_;
+
+ my $filename = File::Spec->catfile(Logdir(), "$hash->{'FUUID'}.data");
+ if (sysopen(my $fh, $filename, O_RDONLY)) {
+ binmode $fh, ':encoding(UTF-8):crlf';
+ my @lines = readline $fh;
+ say @lines;
+ close($fh);
+ } else {
+ say "error $!";
+ };
+};
+
+sub SchellenbergHandle_ProcessMsg {
+ my ($hash, $mt, $fn, $mc, $lc, $rssi) = @_;
+
+ my $unknown1 = hex($lc) & 3; # 2 lsb in message repetition. Only seen as zero
+ $lc = hex($lc) >> 2; # right shift 2 bit / message repetition
+ $rssi = hex($rssi) - 256;
+ Log3 ($hash, 4, sprintf('type: %s, fn: %s, mc: %s, lc: %s, rssi: %s dBm, unkown: %s', $mt, $fn, $mc, $lc, $rssi, $unknown1));
+
+ my sub statefn {
+ my ($fncode) = shift;
+ my $table = {
+ '3B' => 'tilted',
+ '1B' => 'open',
+ '1A' => 'closed',
+ '18' => 'alarm',
+ '19' => 'alarm-end',
+ };
+ return exists($table->{$fncode})?$table->{$fncode}:$fncode;
+ };
+
+ my sub watchdog {
+ readingsBeginUpdate($hash);
+ readingsBulkUpdateIfChanged($hash, 'state', 'dead');
+ readingsBulkUpdateIfChanged($hash, 'alive', 'dead');
+ readingsEndUpdate($hash, 1);
+ };
+
+ if ($mt eq '1') {
+ # message counter > last known ?
+ $mc = hex($mc);
+ my $lastmc = $hash->{'.MC'} // hex($mc) -1;
+ my $diff;
+ {
+ use integer;
+ $diff = (0x10000 + hex($mc) - $lastmc) & 0xFFFF;
+ };
+ if ($diff == 0) {
+ return;
+ } elsif ($diff < 256) {
+ #$hash->{'MISSED_PACKET'} += $lc;
+ readingsBeginUpdate($hash);
+ #readingsBulkUpdate($hash, '.mc', hex($mc));
+ $hash->{'.MC'} = hex($mc);
+ readingsBulkUpdateIfChanged($hash, 'state', statefn($fn));
+ readingsBulkUpdateIfChanged($hash, 'alive', 'ok');
+ readingsBulkUpdate($hash, 'rssi', $rssi);
+ readingsEndUpdate($hash, 1);
+ SchellenbergHandle_SetPersistentData($hash, $mc, 0) if ($mc % 8 == 0);
+ } else {
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash, 'state', 'out-of-sync');
+ eadingsBulkUpdateIfChanged($hash, 'alive', 'ok');
+ readingsBulkUpdate($hash, 'rssi', $rssi);
+ readingsEndUpdate($hash, 1);
+ };
+ } elsif ($mt eq '0') {
+ my $f = hex($mc) >> 8;
+ if ($f == 0x84) {
+ RemoveInternalTimer($hash, \&watchdog);
+ readingsBeginUpdate($hash);
+ my $battery = sprintf('%.1f', (hex($mc) & 0xFF) / 10);
+ readingsBulkUpdateIfChanged($hash, 'state', 'alive') if ($hash->{'STATE'} eq 'dead');
+ readingsBulkUpdateIfChanged($hash, 'voltage', $battery);
+ readingsBulkUpdateIfChanged($hash, 'battery', ($battery > 2.0)?'ok':'low');
+ readingsBulkUpdateIfChanged($hash, 'alive', 'ok');
+ readingsBulkUpdate($hash, 'rssi', $rssi);
+ readingsEndUpdate($hash, 1);
+ InternalTimer(Time::HiRes::time() + 86400, \&watchdog, $hash);
+ #InternalTimer(Time::HiRes::time() + 5, \&watchdog, $hash);
+ };
+ };
+};
+
+sub SchellenbergHandle_Parse {
+ my ($io_hash, $msg) = @_;
+
+ if (my ($mt, $id, $fn, $mc, $lc, $rssi) = ($msg =~ m/^ss
+ ([[:xdigit:]]{1})
+ 4
+ ([[:xdigit:]]{6})
+ ([[:xdigit:]]{2})
+ ([[:xdigit:]]{4})
+ ([[:xdigit:]]{2})
+ ([[:xdigit:]]{2})/x)) {
+
+ my $hash = $modules{'SchellenbergHandle'}{'defptr'}{$id};
+
+ if (defined($hash) and exists($hash->{'NAME'}) and exists($defs{$hash->{'NAME'}})) {
+ SchellenbergHandle_ProcessMsg($hash, $mt, $fn, $mc, $lc, $rssi);
+ return $hash->{'NAME'};
+ } else {
+ # pair cmd
+ if (1 or exists($io_hash->{'PAIRING'}) and $io_hash->{'PAIRING'} and $mt eq '1' and $fn eq '40') {
+ return "UNDEFINED SchellenbergHandle_$id SchellenbergHandle $id";
+ } else {
+ Log3 ($io_hash, 3, sprintf('SchellenbergHandle: unpaired handle %s', $id)) if (lc eq '00');
+ return (undef);
+ };
+ };
+ };
+ # print POSIX::strftime '%Y.%m.%d %H:%M:%S: ', localtime();
+ # print "incoming SOME -----> msg $msg -> $mt $id $fn $mc $lc\r";
+ return;
+};
+
+1;
+
+=pod
+=item device
+=item summary Schellenberg RF Alarm Door Handle
+=item summary_DE Schellenberg Funk-Sicherheits-Alarmgriff
+=begin html
+
+
+
SchellenbergHandle
+
+ Schellenberg RF Alarm Door Handle.
+
+
+
+ Define
+
+ define <name> SchellenbergHandle <ID>
+
+ The device should be installed via autocreate.
+
+ - Install a Schellenberg USB dongle and the associated device (Schellenberg)
+ - Activate pair seconds there
+ - Pair the door handle as described in the manual (handle up, left switch)
+
+
+
+
+
+ Get
+
+
+
+=end html
+
+=cut
\ No newline at end of file