2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-15 16:19:11 +00:00

58_RPI_1Wire: Added support for setting switches, added clear command

git-svn-id: https://svn.fhem.de/fhem/trunk@28020 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Adimarantis 2023-10-03 20:05:43 +00:00
parent 125ec42c56
commit 522e60579d
2 changed files with 145 additions and 81 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
- change 58_RPI_1Wire: Added: Support for setting switches, clear command
- new: 76_SolarForecast: solar predictions of PV systems/Consumer control
- new: 70_PylonLowVoltage: new module for Pylontech low volt batteries
- change: 88_HMCCU: Fixed bug in GetDutyCycle

View File

@ -3,9 +3,6 @@
#and various extension to the GPIO4 Module by members of the FHEM forum
#and RoBue to access 1-Wire-Clones with ID: 28 53 44 54 xx xx xx
#Possible Extensions:
#Writing to the switches is not supported (but I also don't have the HW to test that)
package main;
use strict;
use warnings;
@ -75,16 +72,6 @@ sub RPI_1Wire_Notify {
sub RPI_1Wire_Define { #
my ($hash, $def) = @_;
Log3 $hash->{NAME}, 2, $hash->{NAME}." Define: $def";
$hash->{setList} = {
"update" => "noArg",
"scan" => "noArg",
"precision" => "9,10,11,12",
"conv_time" => "textField",
"therm_bulk_read" => "on,off",
};
$hash->{getList}= {
"udev" => "noArg",
};
$hash->{NOTIFYDEV} = "global";
if ($init_done) {
@ -106,6 +93,11 @@ sub RPI_1Wire_Init { #
}
my @a = split("[ \t]+", $args);
my $force=0;
if (@a==2) {
$force=1 if ($a[1] eq "force");
pop @a;
}
if (@a!=1) {
return "syntax: define <name> RPI_1Wire <id>|BUSMASTER|DHT11-<gpio>|DHT22-<gpio>";
}
@ -124,7 +116,7 @@ sub RPI_1Wire_Init { #
$id=abs($2) if defined $2; #abs to get rid of the "-"
if (! -e $ms_path.$id) {
readingsSingleUpdate($hash,"failreason","Device not found",0);
return "Device $device $id does not exist";
return "Device $device $id does not exist" if $force==0;
}
} elsif ($arg =~ /DHT(11|22)-(\d+)/) {
return "Module RPi::DHT missing (see https://github.com/bublath/rpi-dht)" if defined $DHT_missing;
@ -132,7 +124,7 @@ sub RPI_1Wire_Init { #
$family="DHT".$1;
$device=$family;
} else {
return "Device $arg does not exist" if (! -e "$w1_path/$arg" and $check==1); #Only quit if coming from interactive define
return "Device $arg does not exist" if (! -e "$w1_path/$arg" and $check==1 and $force==0); #Only quit if coming from interactive define
($family, $id) = split('-',$arg);
return "Unknown device family $family" if !defined $RPI_1Wire_Devices{$family};
$device=$RPI_1Wire_Devices{$family}{name};
@ -142,31 +134,19 @@ sub RPI_1Wire_Init { #
$hash->{family}=$family;
my $type=$RPI_1Wire_Devices{$family}{type};
if ($type ne "temperature") {
delete($hash->{setList}{precision});
delete($hash->{setList}{conv_time});
} else {
if (!(-w "$w1_path/$arg/conv_time")) {
delete($hash->{setList}{conv_time});
$hash->{helper}{write}.="conv_time " if (-e "$w1_path/$arg/conv_time");
# Check existance and write permissions to print udev hint
if ($type eq "temperature") {
if (-e "$w1_path/$arg/conv_time" && !-w "$w1_path/$arg/conv_time") {
$hash->{helper}{write}.="conv_time ";
}
if (!(-w "$w1_path/$arg/resolution")) {
delete($hash->{setList}{precision});
$hash->{helper}{write}.="resolution " if (-e "$w1_path/$arg/resolution") ;
if (-e "$w1_path/$arg/resolution" && !-w "$w1_path/$arg/resolution") {
$hash->{helper}{write}.="resolution ";
}
}
#remove set commands that make no sense
if ($device ne "BUSMASTER") {
delete($hash->{setList}{scan});
delete($hash->{setList}{therm_bulk_read});
} else {
my $bulk=ReadingsVal($name,"therm_bulk_read","off");
if (! -w $ms_path.$id."/therm_bulk_read") {
delete($hash->{setList}{therm_bulk_read});
delete($hash->{setList}{update});
readingsSingleUpdate($hash, 'therm_bulk_read', "off",0);
} elsif ($bulk eq "on") {
$hash->{setList}{update}="noArg"; #Restore set command in case it was deleted previously
if ($device eq "BUSMASTER") {
if (!-w "$w1_path/$arg/therm_bulk_read") {
readingsSingleUpdate($hash, 'therm_bulk_read', "off",0);
$hash->{helper}{write}.="therm_bulk_read " if (-e $ms_path.$id."/therm_bulk_read" )
}
}
RPI_1Wire_Set($hash, $name, "setfromreading");
@ -210,6 +190,7 @@ sub RPI_1Wire_GetDevices {
close($fh);
}
$hash->{devices}=join(" ",@devices);
$hash->{devcnt}=scalar(@devices);
return;
}
@ -306,32 +287,97 @@ sub RPI_1Wire_SetConversion {
return;
}
sub RPI_1Wire_Switch {
my ($hash,$switch)= @_;
Log3 $hash->{NAME}, 3, $hash->{NAME}.": Switching $hash->{DEF} to $switch";
my $fh;
my $path="$w1_path/$hash->{DEF}/output";
if (open($fh, ">", $path)) {
print $fh pack("C",$switch);
close($fh);
} else {
return "Error writing to $w1_path/$hash->{DEF}/output";
}
#After setting switch, read back to set readings correctly
my $ret=RPI_1Wire_Poll($hash);
RPI_1Wire_FinishFn($ret);
return;
}
sub RPI_1Wire_Set {
my ( $hash, $name, @args ) = @_;
return unless defined $hash->{setList};
my %sets=%{$hash->{setList}};
### Check Args
my $numberOfArgs = int(@args);
return "RPI_1Wire_Set: No cmd specified for set" if ( $numberOfArgs < 1 );
my $device=$hash->{DEF};
my $family=$hash->{family};
my $id=$hash->{id};
my $type=$RPI_1Wire_Devices{$family}{type};
my $cmd = shift @args;
if (!exists($sets{$cmd})) {
my @cList;
foreach my $k (keys %sets) {
my $opts = undef;
$opts = $sets{$k};
my $sets="";
if ($type eq "temperature") {
if (-w "$w1_path/$device/conv_time") {
$sets.="conv_time:textField ";
}
if (-w "$w1_path/$device/resolution") {
$sets.="precision:9,10,11,12 ";
}
}
if ($type eq "switch" or $type eq "2p-switch") {
$sets.="pioa:on,off piob:on,off ";
}
if ($type eq "8p-switch") {
for my $i ((0..7)) {
$sets.="pio".$i.":on,off ";
}
}
if ($device =~ /(BUSMASTER)(-\d)?$/) {
$sets.="scan:noArg ";
if (-w $ms_path.$id."/therm_bulk_read") {
$sets.="therm_bulk_read:on,off ";
my $bulk=ReadingsVal($name,"therm_bulk_read","off");
$sets.="update:noArg " if ($bulk eq "on");
}
} else {
$sets.="update:noArg clear:noArg ";
}
if (defined($opts)) {
push(@cList,$k . ':' . $opts);
if ( $cmd eq "?" ) {
return "RPI_1Wire_Set: Unknown argument $cmd, choose one of " . $sets;
}
if ($cmd =~ /^pio(a|b)/ and @args==1) {
my $val=shift @args;
Log3 $name, 3, $name." set pio $1 $val\n";
my $set=0;
if ($cmd eq "pioa") {
$set=ReadingsVal($name,"piob","0")*2+($val eq "on"?1:0);
} else {
$set=ReadingsVal($name,"pioa","0")+($val eq "on"?1:0)*2;
}
if ($type eq "2p-switch") {
$set=($set^3); #bits are inverted on DS2413
}
return RPI_1Wire_Switch($hash,$set);
}
if ($cmd =~ /^pio(\d)/ and @args==1) {
my $val=shift @args;
Log3 $name, 3, $name." set pio $1 $val\n";
my $id=$1;
my $set=0;
for my $i (0..7) {
my $bit=2**$i;
if ($i==$id) {
$set+=($val eq "on"?1:0)*$bit;
} else {
push (@cList,$k);
$set+=ReadingsVal($name,"pio".$i,"0")*$bit;
}
}
return "RPI_1Wire_Set: Unknown argument $cmd, choose one of " . join(" ", @cList);
} # error unknown cmd handling
return RPI_1Wire_Switch($hash,$set);
}
if ($cmd eq "precision" and @args==1) {
my $ret=RPI_1Wire_SetPrecision($hash,$args[0]);
return $ret if defined $ret;
@ -339,6 +385,9 @@ sub RPI_1Wire_Set {
} elsif ($cmd eq "scan") {
RPI_1Wire_GetDevices($hash);
return;
} elsif ($cmd eq "clear") {
readingsSingleUpdate($hash, 'failreason', "cleared",0);
readingsSingleUpdate($hash, 'failures', "0",0);
} elsif ($cmd eq "update") {
RPI_1Wire_GetConfig($hash);
return RPI_1Wire_DeviceUpdate($hash);
@ -346,6 +395,8 @@ sub RPI_1Wire_Set {
my $ret=RPI_1Wire_SetConversion($hash,$args[0]);
return $ret if defined $ret;
RPI_1Wire_GetConfig($hash);
} elsif ($cmd eq "switch" and @args==1) {
return RPI_1Wire_Switch($hash,$args[0]);
} elsif ($cmd eq "therm_bulk_read" and @args==1) {
if ($args[0] eq "on") {
readingsSingleUpdate($hash, 'therm_bulk_read', "on",1);
@ -353,39 +404,23 @@ sub RPI_1Wire_Set {
} else {
readingsSingleUpdate($hash, 'therm_bulk_read', "off",1);
}
} else {
return "RPI_1Wire_Set: Unknown argument $cmd, choose one of " . $sets;
}
return;
}
sub RPI_1Wire_Get {
my ($hash, $name, @args) = @_;
return unless defined $hash->{getList};
my $family=$hash->{family};
return unless defined $family;
my $type=$RPI_1Wire_Devices{$family}{type};
return unless $type eq "temperature";
return unless $hash->{helper}{write} ne "";
my %gets=%{$hash->{getList}};
my $numberOfArgs = int(@args);
return "RPI_1Wire_Get: No cmd specified for get" if ( $numberOfArgs < 1 );
my $cmd = shift @args;
if (!exists($gets{$cmd})) {
my @cList;
foreach my $k (keys %gets) {
my $opts = undef;
$opts = $gets{$k};
if (defined($opts)) {
push(@cList,$k . ':' . $opts);
} else {
push (@cList,$k);
}
}
return "RPI_1Wire_Get: Unknown argument $cmd, choose one of " . join(" ", @cList);
} # error unknown cmd handling
if ($cmd eq "udev") {
my $ret= "In order to be able to use some functionality , write access to certain devices is required\n";
$ret.= "Recommended way is to create a udev script\n\n";
@ -400,7 +435,7 @@ sub RPI_1Wire_Get {
return $ret.$script;
}
return;
return "RPI_1Wire_Get: Unknown argument $cmd, choose one of udev:noArg";
}
sub RPI_1Wire_GetConfig {
@ -432,23 +467,26 @@ sub RPI_1Wire_GetConfig {
sub RPI_1Wire_Poll {
my $start = [gettimeofday];
my ($hash) = @_;
my $device=$hash->{DEF};
my $family=$hash->{family};
my $type=$RPI_1Wire_Devices{$family}{type};
my @path=split(",",$RPI_1Wire_Devices{$family}{path});
my $id=$hash->{id};
my $name=$hash->{NAME};
my $device=$family."-".$id;
my $temperature;
my $humidity;
my @counter;
return if ($device eq "BUSMASTER");
return if ($family eq "BUSMASTER");
my $retval=$name;
my $file="";
my @data;
foreach (@path) {
$file="$w1_path/$device/$_";
#if ($type =~ /switch/) {
# $file="/home/pi/state";
#}
if ($family =~ /DHT(11|22)/) {
my $env = RPi::DHT->new($id,$1,1);
Log3 $name, 4 , $name.": Using RPi::DHT for $id DHT-$1";
@ -530,19 +568,23 @@ sub RPI_1Wire_Poll {
##################### UNTESTED ####################
if ($type =~ /switch/) {
my $pins=unpack("c",$data[0]); # Binary bits
my $pins=unpack("C",$data[0]); # Binary bits
my $data_bin=sprintf("%008b", $pins);
my @pio=split("",$data_bin);
my @pio=reverse(split("",$data_bin)); #Array order is backwards from bit order
Log3 $hash->{NAME}, 5, $hash->{NAME}.": Switch state:".join(",",@pio);
if ($type eq "8p-switch") {
my $pin=7;
my $pin=0;
foreach (@pio) {
$retval.=" pio".$pin."=".$_;
$retval.=" pio".$pin++."=".$_;
}
} else {
$retval.= " pioa=".$pio[0];
if ($type eq "2p-switch") {
$retval.= " piob=".$pio[2];
$retval.= " pioa=".($pio[0]^1);
$retval.= " latcha=".($pio[1]^1);
$retval.= " piob=".($pio[2]^1);
$retval.= " latchb=".($pio[3]^1);
} else {
$retval.= " pioa=".$pio[0];
$retval.= " piob=".$pio[1];
}
}
@ -669,14 +711,14 @@ For German documentation see <a href="https://wiki.fhem.de/wiki/RPI_1Wire">Wiki<
<ul>
provides an interface to devices connected through the standard Raspberry 1-Wire interface (GPIO4) and is aware of the following devices:<br><br>
<li>Family 0x10 (DS18S20) temperature</li>
<li>Family 0x12 (DS2406) adressable 2 port switch (read only, untested)</li>
<li>Family 0x12 (DS2406) adressable 2 port switch (untested)</li>
<li>Family 0x19 (DS28E17) i2c bridge (unsupported)</li>
<li>Family 0x1c (DS28E04) eeprom memory (unsupported)</li>
<li>Family 0x1d (DS2423) dual counter</li>
<li>Family 0x26 (DS2438) a/d converter with temperature support</li>
<li>Family 0x28 (DS18B20) temperature</li>
<li>Family 0x29 (DS2408) 8 port switch (read only, untested)</li>
<li>Family 0x3a (DS2413) adressable 2 port switch (read only, untested)</li>
<li>Family 0x29 (DS2408) 8 port switch (untested)</li>
<li>Family 0x3a (DS2413) adressable 2 port switch</li>
<li>Family 0x3b (DS1825) temperature</li>
<li>Family 0x42 (DS28EA00) temperature</li>
<li>DHT11/DHT22 sensors (adressable GPIO) temperature, humidity</li>
@ -691,6 +733,7 @@ For German documentation see <a href="https://wiki.fhem.de/wiki/RPI_1Wire">Wiki<
Having a BUSMASTER is not required unless you like to use autocreate or the therm_bulk_read feature.<br>
The internal reading "devices" will list all the device IDs associated with the BUSMASTER.<br>
In case you defined more than one w1_bus_master in your system, you can use "BUSMASTER-x", where "x" is the number of w1_bus_master<b>x</b> in the sysfs, to explicitly define it. Default is always to use "1".<br>
Note: To define a FHEM device for a device that is physically not present, provide "force" as second argument to override the check.<br>
</li>
<li>ff-xxxxxxxxxxxx is the id of a 1-Wire device as shown in sysfs tree where ff is the family. To use 1-Wire sensors call "sudo raspi-config" and enable the "1-Wire Interface" under "Interface options".</li>
<li>DHT11|12-&lt;gpio&gt defines a DHT11 or DHT22 sensor where gpio is the number of the used GPIO. This requires an additional Perl module which can be aquired <a href="https://github.com/bublath/rpi-dht">here</a>. Make sure to define the right type, since DHT11 and DHT22 sensors are similar, but require different algorithms to read. Also note that these are not 1-Wire (GPIO4) sensors and should be attached to GPIOs different to 4 and require one GPIO each.</li>
@ -729,6 +772,22 @@ For German documentation see <a href="https://wiki.fhem.de/wiki/RPI_1Wire">Wiki<
Requires Linux Kernel 5.10+ (Raspbian Buster) and write permissions to sysfs (see "get udev")<br>
<b>Note:</b> There seems to be a Kernel bug, that breaks this feature if there are other 1-Wire devices on GPIO4 than temperature sensors using w1_therm driver.<br>
</li>
<li><b>set clear</b><br>
<a id="RPI_1Wire-set-clear"></a>
Clears the failures counter.
</li>
<li><b>set pioa on|off</b><br>
<a id="RPI_1Wire-set-pioa"></a>
Sets the pioa of a 2 port switch to on or off.
</li>
<li><b>set piob on|off</b><br>
<a id="RPI_1Wire-set-piob"></a>
Sets the piob of a 2 port switch to on or off.
</li>
<li><b>set pio(0-7) on|off</b><br>
<a id="RPI_1Wire-set-pio0"></a>
Sets the according pio of a 8 port switch (0-7) to on or off. (untested)
</li>
</ul>
<a id="RPI_1Wire-get"></a>
@ -786,12 +845,14 @@ For German documentation see <a href="https://wiki.fhem.de/wiki/RPI_1Wire">Wiki<
<ul>
<br>
<li><b>failures</b></li>
Counts the failed read attempts (due to unavaible devices, empty data or CRC failures)<br>
Counts the failed read attempts (due to unavaible devices, empty data or CRC failures).<br>
Use "set clear" to reset this counter (e.g. after a hardware issue was resolved)<br>
<li><b>failreason</b></li>
Reason for the last seen failure:
<li>crc: data could be read, but there was a checksum failure. If that happens too often, check you cabling quality</li>
<li>no_data: The device could be opened, but no data was received</li>
<li>open_device: The device could not be opened. Likely it was disconnected</li>
<li>cleared: The counter has just been cleared with "set clear"</li>
<li><b>duration</b></li>
Duration of the last read out. In modes blocking and timer a warning is put into failreason if this get more than 0.5s<br>
<li><b>conv_time</b></li>
@ -808,6 +869,8 @@ For German documentation see <a href="https://wiki.fhem.de/wiki/RPI_1Wire">Wiki<
Voltage readings from the device (DS2438)<br>
<li><b>pioa/piob</b></li>
Switch states for dual port switches<br>
<li><b>latcha/latchb</b></li>
Latch states for DS2413. This device differentiates between the actual state of the switch and the the state of the current request (latch).<br>
<li><b>pio1 ... pio8</b></li>
Switch states for 8 port switches<br>
</ul>