52_I2C_MCP23017.pm 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. ##############################################################################
  2. # $Id: 52_I2C_MCP23017.pm 12059 2016-08-22 21:14:59Z klauswitt $
  3. ##############################################################################
  4. # Modul for I2C GPIO Extender MCP23017
  5. #
  6. # contributed by Klaus Wittstock (2013) email: klauswittstock bei gmail
  7. ##############################################################################
  8. package main;
  9. use strict;
  10. use warnings;
  11. use SetExtensions;
  12. use Scalar::Util qw(looks_like_number);
  13. no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  14. my %Registers = (
  15. 'IODIRA' => 0x00, #1 = input; 0 = output (default 1)
  16. 'IODIRB' => 0x01,
  17. 'IPOLA' => 0x02, #1 inverts logic (default 0)
  18. 'IPOLB' => 0x03,
  19. 'GPINTENA' => 0x04, #1 enables the pin for interrupt-on-change (default 0)
  20. 'GPINTENB' => 0x05,
  21. 'DEFVALA' => 0x06, #The default comparison value for interrupt (opposite value will caues an interrupt) (default 0)
  22. 'DEFVALB' => 0x07,
  23. 'INTCONA' => 0x08, #If a bit is set, the corresponding I/O pin is compared against DEFVAL register. Otherwise against the previous value.
  24. 'INTCONB' => 0x09,
  25. 'IOCON' => 0x0A,
  26. 'GPPUA' => 0x0C, #100k pull up resistor for input
  27. 'GPPUB' => 0x0D,
  28. 'INTFA' => 0x0E, #shows which Pin caused the interrupt (ro)
  29. 'INTFB' => 0x0F,
  30. 'INTCAPA' => 0x10, #status from all registers at the time the interrupt occured, remain unchanged until a read of INTCAP or GPIO. (ro)
  31. 'INTCAPB' => 0x11,
  32. 'GPIOA' => 0x12, #value on the ports (r/w)
  33. 'GPIOB' => 0x13,
  34. 'OLATA' => 0x14,
  35. 'OLATB' => 0x15,
  36. );
  37. my %setsP = (
  38. 'off' => 0,
  39. 'on' => 1,
  40. );
  41. my %intout = (
  42. 'separate_active-low' => 0x00,
  43. 'separate_active-high' => 0x02,
  44. 'separate_open-drain' => 0x04,
  45. 'connected_active-low' => 0x40,
  46. 'connected_active-high' => 0x42,
  47. 'connected_open-drain' => 0x44,
  48. );
  49. ###############################################################################
  50. sub I2C_MCP23017_Initialize($) {
  51. my ($hash) = @_;
  52. $hash->{DefFn} = "I2C_MCP23017_Define";
  53. $hash->{InitFn} = 'I2C_MCP23017_Init';
  54. $hash->{UndefFn} = "I2C_MCP23017_Undefine";
  55. $hash->{AttrFn} = "I2C_MCP23017_Attr";
  56. $hash->{StateFn} = "I2C_MCP23017_State";
  57. $hash->{SetFn} = "I2C_MCP23017_Set";
  58. $hash->{GetFn} = "I2C_MCP23017_Get";
  59. $hash->{I2CRecFn} = "I2C_MCP23017_I2CRec";
  60. $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0 ".
  61. "poll_interval OnStartup ".
  62. "OutputPorts:multiple-strict,A0,A1,A2,A3,A4,A5,A6,A7,B0,B1,B2,B3,B4,B5,B6,B7 ".
  63. "Pullup:multiple-strict,A0,A1,A2,A3,A4,A5,A6,A7,B0,B1,B2,B3,B4,B5,B6,B7 ".
  64. "invert_input:multiple-strict,A0,A1,A2,A3,A4,A5,A6,A7,B0,B1,B2,B3,B4,B5,B6,B7 ".
  65. "Interrupt:multiple-strict,A0,A1,A2,A3,A4,A5,A6,A7,B0,B1,B2,B3,B4,B5,B6,B7 ".
  66. "InterruptOut:separate_active-low,separate_active-high,separate_open-drain,connected_active-low,connected_active-high,connected_open-drain ".
  67. "$readingFnAttributes";
  68. }
  69. ###############################################################################
  70. sub I2C_MCP23017_Define($$) {
  71. my ($hash, $def) = @_;
  72. my @a = split("[ \t]+", $def);
  73. $hash->{STATE} = 'defined';
  74. if ($main::init_done) {
  75. eval { I2C_MCP23017_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  76. return I2C_MCP23017_Catch($@) if $@;
  77. }
  78. return undef;
  79. }
  80. ###############################################################################
  81. sub I2C_MCP23017_Init($$) { #Geraet beim anlegen/booten/nach Neuverbindung (wieder) initialisieren
  82. my ( $hash, $args ) = @_;
  83. if (defined $args && int(@$args) != 1) {
  84. return "Define: Wrong syntax. Usage:\n" .
  85. "define <name> I2C_MCP23017 <i2caddress>";
  86. }
  87. if (defined (my $address = shift @$args)) {
  88. $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
  89. } else {
  90. return "$hash->{NAME} I2C Address not valid";
  91. }
  92. AssignIoPort($hash);
  93. my $msg = '';
  94. #Output level wieder setzen
  95. my $sbyte = 0;
  96. foreach (reverse 0..7) {
  97. $sbyte += $setsP{ReadingsVal($hash->{NAME},"PortA".$_,"off")} << ($_); #Werte fuer PortA aus dem Reading holen
  98. $sbyte += $setsP{ReadingsVal($hash->{NAME},"PortB".$_,"off")} << (8 + $_);
  99. }
  100. $msg = I2C_MCP23017_SetRegPair($hash, $sbyte, "GPIO") if $sbyte;
  101. #bei Init IC neu konfigurieren
  102. if ( defined ( my $val = AttrVal($hash->{NAME},"invert_input",undef)) ) {
  103. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, "invert_input", $val);
  104. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "IPOL") unless $msg;
  105. }
  106. if ( defined ( my $val = AttrVal($hash->{NAME},"OutputPorts",undef)) ) {
  107. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, "OutputPorts", $val);
  108. $msg = I2C_MCP23017_SetRegPair($hash, ~$regval, "IODIR") unless $msg;
  109. }
  110. if ( defined ( my $val = AttrVal($hash->{NAME},"Pullup",undef)) ) {
  111. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, "Pullup", $val);
  112. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "GPPU") unless $msg;
  113. }
  114. if ( defined ( my $val = AttrVal($hash->{NAME},"Interrupt",undef)) ) {
  115. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, "Interrupt", $val);
  116. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "GPINTEN") unless $msg;
  117. }
  118. if ( defined ( my $val = AttrVal($hash->{NAME},"InterruptOut",undef)) ) {
  119. my $regval = 0;
  120. $regval = $intout{$val} if defined $val;
  121. if (defined (my $iodev = $hash->{IODev})) {
  122. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  123. direction => "i2cwrite",
  124. i2caddress => $hash->{I2C_Address},
  125. reg => $Registers{IOCON},
  126. data => $regval,
  127. }) if (defined $hash->{I2C_Address});
  128. } else {
  129. return "no IODev assigned to '$hash->{NAME}'";
  130. }
  131. }
  132. #Output level wieder setzen die zweite
  133. #$sbyte = 0;
  134. #foreach (reverse 0..7) {
  135. # $sbyte += $setsP{ReadingsVal($hash->{NAME},"PortA".$_,"off")} << ($_); #Werte fuer PortA aus dem Reading holen
  136. # $sbyte += $setsP{ReadingsVal($hash->{NAME},"PortB".$_,"off")} << (8 + $_);
  137. #}
  138. #$msg = I2C_MCP23017_SetRegPair($hash, $sbyte, "GPIO") if $sbyte;
  139. I2C_MCP23017_Get($hash, $hash->{NAME});
  140. $hash->{STATE} = 'Initialized';
  141. return ($msg) ? $msg : undef;
  142. }
  143. ###############################################################################
  144. sub I2C_MCP23017_Catch($) { #Fehlermeldung von eval formattieren
  145. my $exception = shift;
  146. if ($exception) {
  147. $exception =~ /^(.*)( at.*FHEM.*)$/;
  148. return $1;
  149. }
  150. return undef;
  151. }
  152. ###############################################################################
  153. sub I2C_MCP23017_Undefine($$) {
  154. my ($hash, $arg) = @_;
  155. if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) {
  156. RemoveInternalTimer($hash);
  157. }
  158. }
  159. ###############################################################################
  160. sub I2C_MCP23017_Attr(@) {
  161. my ($command, $name, $attr, $val) = @_;
  162. my $hash = $defs{$name};
  163. my $msg = '';
  164. if ($command && $command eq "set" && $attr && $attr eq "IODev") {
  165. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
  166. main::AssignIoPort($hash,$val);
  167. my @def = split (' ',$hash->{DEF});
  168. I2C_MCP23017_Init($hash,\@def) if (defined ($hash->{IODev}));
  169. }
  170. }
  171. if ($attr && $attr eq 'poll_interval') {
  172. #my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  173. if (!defined($val) ) {
  174. RemoveInternalTimer($hash);
  175. } elsif ($val > 0) {
  176. RemoveInternalTimer($hash);
  177. InternalTimer(1, 'I2C_MCP23017_Poll', $hash, 0);
  178. } else {
  179. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  180. }
  181. } elsif ($attr && $attr eq "OutputPorts") {
  182. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, $attr, $val);
  183. $msg = I2C_MCP23017_SetRegPair($hash, ~$regval, "IODIR") unless $msg;
  184. } elsif ($attr && $attr eq "Pullup") {
  185. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, $attr, $val);
  186. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "GPPU") unless $msg;
  187. } elsif ($attr && $attr eq "invert_input") {
  188. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, $attr, $val);
  189. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "IPOL") unless $msg;
  190. } elsif ($attr && $attr eq "Interrupt") {
  191. ($msg, my $regval) = I2C_MCP23017_CheckAttr($hash, $attr, $val);
  192. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "GPINTEN") unless $msg;
  193. } elsif ($attr && $attr eq "OnStartup") {
  194. if (defined $val) {
  195. foreach (split (/,/,$val)) {
  196. my @pair = split (/=/,$_);
  197. $msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated <port>=on|off|last where <port> = A0 - A7 and/or B0 - B7"
  198. unless ( scalar(@pair) == 2 &&
  199. $pair[0] =~ m/^(A|B)(0|)[0-7]$/i &&
  200. $pair[1] =~ m/^(on|off|last)$/i);
  201. }
  202. }
  203. } elsif ($attr && $attr eq "InterruptOut") {
  204. my $regval = 0;
  205. if (defined $val) {
  206. return "wrong value: $_ for \"attr $hash->{NAME} $attr\" use one of: " .
  207. join(',', (sort { $intout{ $a } <=> $intout{ $b } } keys %setsP) )
  208. unless(exists($intout{$val}));
  209. $regval = $intout{$val};
  210. }
  211. if (defined (my $iodev = $hash->{IODev})) {
  212. #Log3 $hash, 1, "schreibe raus: i2cwrite|$hash->{I2C_Address}|$Registers{$regtype . $reg}|$port{$reg}|";
  213. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  214. direction => "i2cwrite",
  215. i2caddress => $hash->{I2C_Address},
  216. reg => $Registers{IOCON},
  217. data => $regval,
  218. }) if (defined $hash->{I2C_Address});
  219. } else {
  220. return "no IODev assigned to '$hash->{NAME}'";
  221. }
  222. }
  223. return ($msg) ? $msg : undef;
  224. }
  225. ###############################################################################
  226. sub I2C_MCP23017_State($$$$) { #reload readings at FHEM start
  227. my ($hash, $tim, $sname, $sval) = @_;
  228. Log3 $hash, 4, "$hash->{NAME}: $sname kann auf $sval wiederhergestellt werden $tim";
  229. if ($sname =~ m/^Port(A|B)(0|)[0-7]$/i) {
  230. my $po = substr $sname, 4, 2; # Ax oder Bx
  231. Log3 $hash, 5, "$hash->{NAME}: Port = $po";
  232. if ( index( AttrVal($hash->{NAME}, "OutputPorts", ""), $po, 0) >= 0 ) {
  233. if ( ( my $pos = index(AttrVal($hash->{NAME},"OnStartup", ""), $po ,0) ) >=0 ) {
  234. my $val = substr AttrVal($hash->{NAME},"OnStartup",undef), $pos + 3, 2;
  235. if ( $val eq "on" ) {
  236. Log3 $hash, 5, "$hash->{NAME}: $sname soll auf on gesetzt werden";
  237. readingsSingleUpdate($hash,$sname, "on", 1);
  238. } elsif ( $val eq "of" ) {
  239. Log3 $hash, 5, "$hash->{NAME}: $sname soll auf off gesetzt werden";
  240. readingsSingleUpdate($hash,$sname, "off", 1);
  241. } else {
  242. Log3 $hash, 5, "$hash->{NAME}: $sname soll auf Altzustand: $sval gesetzt werden";
  243. $hash->{READINGS}{$sname}{VAL} = $sval;
  244. $hash->{READINGS}{$sname}{TIME} = $tim;
  245. }
  246. } else {
  247. Log3 $hash, 5, "$hash->{NAME}: $sname wird auf Altzustand: $sval gesetzt (kein Eintrag in on Startup)";
  248. $hash->{READINGS}{$sname}{VAL} = $sval;
  249. $hash->{READINGS}{$sname}{TIME} = $tim;
  250. }
  251. } else {
  252. Log3 $hash, 5, "$hash->{NAME}: $sname ist Eingang";
  253. }
  254. }
  255. return undef;
  256. }
  257. ###############################################################################
  258. sub I2C_MCP23017_CheckAttr {
  259. my ($hash, $attr, $val) = @_;
  260. my $msg = undef;
  261. my ($regval) = 0;
  262. if (defined $val) {
  263. foreach (split (/,/,$val)) {
  264. $msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated values from A0 - A7 and/or B0 - B7" unless ($_ =~ m/^(A|B)(0|)[0-7]$/i);
  265. my $bank = ($_ =~ m/^A/) ? 0 : 8; # A oder B
  266. $_ =~ tr/[a-zA-Z]//d; #Nummer aus String extrahieren
  267. $regval |= 1 << ($_ + $bank);
  268. }
  269. }
  270. return $msg, $regval;
  271. }
  272. ###############################################################################
  273. sub I2C_MCP23017_SetRegPair { #set register pair for PortA/B
  274. my ($hash, $regval, $regtype) = @_;
  275. my %port = ();
  276. $port{A} = $regval & 0xff;
  277. $port{B} = ( $regval >> 8 ) & 0xff;
  278. if (defined (my $iodev = $hash->{IODev})) {
  279. foreach my $reg (keys %port) {
  280. #Log3 $hash, 1, "schreibe raus: i2cwrite|$hash->{I2C_Address}|$Registers{$regtype . $reg}|$port{$reg}|";
  281. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  282. direction => "i2cwrite",
  283. i2caddress => $hash->{I2C_Address},
  284. reg => $Registers{$regtype . $reg},
  285. data => $port{$reg},
  286. }) if (defined $hash->{I2C_Address});
  287. }
  288. } else {
  289. return "no IODev assigned to '$hash->{NAME}'";
  290. }
  291. }
  292. ###############################################################################
  293. sub I2C_MCP23017_Poll($) { #function for refresh intervall
  294. my ($hash) = @_;
  295. my $name = $hash->{NAME};
  296. # Read values
  297. I2C_MCP23017_Get($hash, $name);
  298. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  299. if ($pollInterval > 0) {
  300. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_MCP23017_Poll', $hash, 0);
  301. }
  302. }
  303. ###############################################################################
  304. sub I2C_MCP23017_Set($@) {
  305. my ($hash, @a) = @_;
  306. my $name =$a[0];
  307. my $cmd = $a[1];
  308. my $val = $a[2];
  309. my @outports = sort(split(/,/,AttrVal($name, "OutputPorts", "")));
  310. unless (@a == 3) {
  311. }
  312. my $msg = undef;
  313. if ( $cmd && $cmd =~ m/^P(ort|)(A|B)((0|)[0-7])(,(P|)(ort|)(A|B)((0|)[0-7])){0,7}$/i) {
  314. return "wrong value: $val for \"set $name $cmd\" use one of: " .
  315. join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) )
  316. unless(exists($setsP{$val}));
  317. my @scmd = split(",", $cmd);
  318. foreach (@scmd) {
  319. $_ =~ tr/P(ort|)//d; #Nummer aus String extrahieren
  320. $msg .= (defined $msg ? "," : "") . "Port" . $_ unless ( ($_) ~~ @outports ); #Pruefen ob entsprechender Port Input ist
  321. }
  322. return "$name error: $msg is defined as input" if $msg;
  323. #Log3 $hash, 1, "$name: multitest gereinigt: @scmd";
  324. my $regval = 0;
  325. foreach (reverse 0..7) {
  326. foreach my $po ("A","B") {
  327. my $bank = ($po eq "A") ? 0 : 8; # A oder B
  328. if ( ($po.$_) ~~ @scmd ) { #->wenn aktueller Port in Liste dann neuer Wert
  329. $regval += $setsP{$val} << ($bank + $_);
  330. } else { #->sonst aus dem Reading holen
  331. $regval += $setsP{ReadingsVal($name,"Port".$po.$_,"off")} << ($bank + $_);
  332. }
  333. }
  334. }
  335. #Log3 $hash, 1, "$name: endwert: $regval";
  336. $msg = I2C_MCP23017_SetRegPair($hash, $regval, "GPIO") unless $msg;
  337. } else {
  338. my $list = "";
  339. foreach (0..7) {
  340. next unless ( ("A" . $_) ~~ @outports ); #Inputs ueberspringen
  341. $list .= "PortA" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
  342. }
  343. foreach (0..7) {
  344. next unless ( ("B" . $_) ~~ @outports ); #Inputs ueberspringen
  345. $list .= "PortB" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
  346. }
  347. $msg = "Unknown argument $a[1], choose one of " . $list;
  348. }
  349. return ($msg) ? $msg : undef;
  350. ###########################################################################################################
  351. #alte einzelportversion
  352. # my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" );
  353. # if ( $cmd && $cmd =~ m/^Port(A|B)(0|)[0-7]$/i) {
  354. # return "wrong value: $val for \"set $name $cmd\" use one of: " .
  355. # join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) )
  356. # unless(exists($setsP{$val}));
  357. # my $po = substr $cmd, 4, 1; # A oder B
  358. # my $regaddr = $po eq "A" ? $Registers{GPIOA} : $Registers{GPIOB}; #Adresse fuer GPIO Register
  359. # substr($cmd,0,5,"");
  360. # return "$name error: Port$po$cmd is defined as input" unless ( ($po . $cmd) ~~ @outports ); #Pruefen ob entsprechender Port Input ist
  361. #
  362. # my $sbyte = 0;
  363. # foreach (reverse 0..7) {
  364. # if ( $_ == $cmd ) { #->wenn aktueller Port dann neuer Wert
  365. # $sbyte += $setsP{$val} << ($_);
  366. # next;
  367. # }
  368. # $sbyte += $setsP{ReadingsVal($name,"Port".$po.$_,"off")} << ($_); #->sonst aus dem Reading holen
  369. # }
  370. #
  371. # $sendpackage{data} = $sbyte;
  372. # $sendpackage{reg} = $regaddr;
  373. # Log3 $hash, 5, "$name set regaddr: " . sprintf("%.2X",$sendpackage{reg}) . " inhalt: " . sprintf("%.2X",$sendpackage{data});
  374. # } else {
  375. # my $list = "";
  376. # foreach (0..7) {
  377. # next unless ( ("A" . $_) ~~ @outports ); #Inputs ueberspringen
  378. # $list .= "PortA" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
  379. # }
  380. # foreach (0..7) {
  381. # next unless ( ("B" . $_) ~~ @outports ); #Inputs ueberspringen
  382. # $list .= "PortB" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
  383. # }
  384. # return "Unknown argument $a[1], choose one of " . $list;
  385. # }
  386. # return "$name: no IO device defined" unless ($hash->{IODev});
  387. # my $phash = $hash->{IODev};
  388. # my $pname = $phash->{NAME};
  389. # CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
  390. }
  391. ###############################################################################
  392. sub I2C_MCP23017_Get($@) {
  393. my ($hash, @a) = @_;
  394. my $name =$a[0];
  395. my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" );
  396. $sendpackage{reg} = 18; #startadresse zum lesen
  397. $sendpackage{nbyte} = 2;
  398. return "$name: no IO device defined" unless ($hash->{IODev});
  399. my $phash = $hash->{IODev};
  400. my $pname = $phash->{NAME};
  401. CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
  402. }
  403. ###############################################################################
  404. sub I2C_MCP23017_I2CRec($@) { #ueber CallFn vom physical aufgerufen
  405. my ($hash, $clientmsg) = @_;
  406. my $name = $hash->{NAME};
  407. my $phash = $hash->{IODev};
  408. my $pname = $phash->{NAME};
  409. while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
  410. $hash->{$k} = $v if $k =~ /^$pname/ ;
  411. }
  412. #hier noch ueberpruefen, ob Register und Daten ok
  413. if ($clientmsg->{direction} && defined $clientmsg->{reg} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
  414. if ($clientmsg->{direction} eq "i2cread" && $clientmsg->{received}) { # =~ m/^[a-f0-9]{2}$/i) {
  415. #my @rec = @{$clientmsg->{received}}; #bei uebergabe im hash als array
  416. my @rec = split(" ",$clientmsg->{received}); #bei uebergabe im als skalar
  417. Log3 $hash, 3, "$name: wrong amount of registers transmitted from $pname" unless (@rec == $clientmsg->{nbyte});
  418. foreach (reverse 0..$#rec) { #reverse, damit Inputs (Register 0 und 1 als letztes geschrieben werden)
  419. I2C_MCP23017_UpdReadings($hash, $_ + $clientmsg->{reg} , $rec[$_]);
  420. }
  421. readingsSingleUpdate($hash,"state", "Ok", 1);
  422. } elsif ($clientmsg->{direction} eq "i2cwrite" && defined $clientmsg->{data}) { # =~ m/^[a-f0-9]{2}$/i) {#readings aktualisieren wenn uebertragung ok
  423. I2C_MCP23017_UpdReadings($hash, $clientmsg->{reg} , $clientmsg->{data}) if ( ($clientmsg->{reg} == $Registers{GPIOA}) || ($clientmsg->{reg} == $Registers{GPIOB}) );
  424. readingsSingleUpdate($hash,"state", "Ok", 1);
  425. } else {
  426. readingsSingleUpdate($hash,"state", "transmission error", 1);
  427. Log3 $hash, 3, "$name: failurei in message from $pname";
  428. Log3 $hash, 3,(defined($clientmsg->{direction}) ? "Direction: " . $clientmsg->{direction} : "Direction: undef").
  429. (defined($clientmsg->{i2caddress}) ? " I2Caddress: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef").
  430. (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef").
  431. (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef").
  432. (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $clientmsg->{received}) : " received: undef");
  433. }
  434. } else {
  435. readingsSingleUpdate($hash,"state", "transmission error", 1);
  436. Log3 $hash, 3, "$name: failure in message from $pname";
  437. Log3 $hash, 3,(defined($clientmsg->{direction}) ? "Direction: " . $clientmsg->{direction} : "Direction: undef").
  438. (defined($clientmsg->{i2caddress}) ? " I2Caddress: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef").
  439. (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef").
  440. (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef").
  441. (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $clientmsg->{received}) : " received: undef");
  442. #my $cmsg = undef;
  443. #foreach my $av (keys %{$clientmsg}) { $cmsg .= "|" . $av . ": " . $clientmsg->{$av}; }
  444. #Log3 $hash, 3, $cmsg;
  445. }
  446. }
  447. ###############################################################################
  448. sub I2C_MCP23017_UpdReadings($$$) { #nach Rueckmeldung readings updaten (ueber I2CRec aufgerufen)
  449. my ($hash, $reg, $inh) = @_;
  450. my $name = $hash->{NAME};
  451. #$inh = hex($inh);
  452. #$reg = hex($reg);
  453. Log3 $hash, 5, "$name UpdReadings Register: $reg, Inhalt: $inh";
  454. readingsBeginUpdate($hash);
  455. if ($reg == $Registers{GPIOA}) {
  456. my %rsetsP = reverse %setsP;
  457. foreach (0..7) {
  458. my $pval = 1 & ( $inh >> $_ );
  459. readingsBulkUpdate($hash, 'PortA'.$_ , $rsetsP{$pval})
  460. if (ReadingsVal($name, 'PortA'.$_,"nix") ne $rsetsP{$pval}); #nur wenn Wert geaendert
  461. }
  462. } elsif ($reg == $Registers{GPIOB}) {
  463. my %rsetsP = reverse %setsP;
  464. foreach (0..7) {
  465. my $pval = 1 & ( $inh >> $_ );
  466. readingsBulkUpdate($hash, 'PortB'.$_ , $rsetsP{$pval})
  467. if (ReadingsVal($name, 'PortB'.$_,"nix") ne $rsetsP{$pval}); #nur wenn Wert geaendert
  468. }
  469. }
  470. readingsEndUpdate($hash, 1);
  471. return;
  472. }
  473. 1;
  474. =pod
  475. =item device
  476. =item summary controls/reads GPIOs from an via I2C connected MCP23017 port extender
  477. =item summary_DE steuern/lesen der GPIOs eines &uuml;ber I2C angeschlossenen MCP23017
  478. =begin html
  479. <a name="I2C_MCP23017"></a>
  480. <h3>I2C_MCP23017</h3>
  481. (en | <a href="commandref_DE.html#I2C_MCP23017">de</a>)
  482. <ul>
  483. <a name="I2C_MCP23017"></a>
  484. Provides an interface to the MCP23017 16 channel port extender IC. On Raspberry Pi the Interrupt Pin's can be connected to an GPIO and <a href="#RPI_GPIO">RPI_GPIO</a> can be used to get the port values if an interrupt occurs.<br>
  485. The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  486. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  487. <b>attribute IODev must be set</b><br>
  488. <a name="I2C_MCP23017Define"></a><br>
  489. <b>Define</b>
  490. <ul>
  491. <code>define &lt;name&gt; I2C_MCP23017 &lt;I2C Address&gt;</code><br>
  492. where <code>&lt;I2C Address&gt;</code> is without direction bit<br>
  493. </ul>
  494. <a name="I2C_MCP23017Set"></a>
  495. <b>Set</b>
  496. <ul>
  497. <code>set &lt;name&gt; &lt;port[,port[...]]&gt; &lt;value&gt;</code><br><br>
  498. where <code>&lt;port&gt;</code> is one of PortA0 to PortA7 / PortB0 to PortB7 and <code>&lt;value&gt;</code> is one of:<br>
  499. <ul>
  500. <code>
  501. off<br>
  502. on<br>
  503. </code>
  504. </ul>
  505. <br>
  506. Example:
  507. <ul>
  508. <code>set mod1 PortA4 on</code><br>
  509. <code>set mod1 PortA4,PortB6 off</code><br>
  510. <code>set mod1 PortA4,B6 on</code><br>
  511. </ul><br>
  512. </ul>
  513. <a name="I2C_MCP23017Get"></a>
  514. <b>Get</b>
  515. <ul>
  516. <code>get &lt;name&gt;</code>
  517. <br><br>
  518. refreshes all readings
  519. </ul><br>
  520. <a name="I2C_MCP23017Attr"></a>
  521. <b>Attributes</b>
  522. <ul>
  523. <li>poll_interval<br>
  524. Set the polling interval in minutes to query the GPIO's level<br>
  525. Default: -, valid values: decimal number<br><br>
  526. </li>
  527. <li>OutputPorts<br>
  528. Comma separated list of ports that are used as Output<br>
  529. Ports not in this list can't be written<br>
  530. Default: no, valid values: A0-A7, B0-B7<br><br>
  531. </li>
  532. <li>OnStartup<br>
  533. Comma separated list of output ports and their desired state after start<br>
  534. Without this atribut all output ports will set to last state<br>
  535. Default: -, valid values: &lt;port&gt;=on|off|last where &lt;port&gt; = A0-A7, B0-B7<br><br>
  536. </li>
  537. <li>Pullup<br>
  538. Comma separated list of input ports which switch on their internal 100k pullup<br>
  539. Default: -, valid values: A0-A7, B0-B7<br><br>
  540. </li>
  541. <li>Interrupt<br>
  542. Comma separated list of input ports which will trigger the IntA/B pin<br>
  543. Default: -, valid values: A0-A7, B0-B7<br><br>
  544. </li>
  545. <li>invert_input<br>
  546. Comma separated list of input ports which use inverted logic<br>
  547. Default: -, valid values: A0-A7, B0-B7<br><br>
  548. </li>
  549. <li>InterruptOut<br>
  550. Configuration options for INTA/INTB output pins<br>
  551. Values:<br>
  552. <ul>
  553. <li>
  554. separate_active-low (INTA/INTB outputs are separate for both ports and active low)
  555. </li>
  556. <li>
  557. separate_active-high (INTA/INTB outputs are separate for both ports and active high)
  558. </li>
  559. <li>
  560. separate_open-drain (INTA/INTB outputs are separate for both ports and open drain)
  561. </li>
  562. <li>
  563. connected_active-low (INTA/INTB outputs are internally connected and active low)
  564. </li>
  565. <li>
  566. connected_active-high (INTA/INTB outputs are internally connected and active high)
  567. </li>
  568. <li>
  569. connected_open-drain (INTA/INTB outputs are internally connected and open drain)
  570. </li><br>
  571. </ul>
  572. </li>
  573. <li><a href="#IODev">IODev</a></li>
  574. <li><a href="#ignore">ignore</a></li>
  575. <li><a href="#do_not_notify">do_not_notify</a></li>
  576. <li><a href="#showtime">showtime</a></li>
  577. </ul>
  578. <br>
  579. </ul>
  580. =end html
  581. =begin html_DE
  582. <a name="I2C_MCP23017"></a>
  583. <h3>I2C_MCP23017</h3>
  584. (<a href="commandref.html#I2C_MCP23017">en</a> | de)
  585. <ul>
  586. <a name="I2C_MCP23017"></a>
  587. Erm&ouml;glicht die Verwendung eines MCP23017 I2C 16 Bit Portexenders.
  588. Auf einem Raspberry Pi kann der Interrupt Pin des MCP23017 mit einem GPIO verbunden werden und &uuml;ber die Interrupt Funktionen von <a href="#RPI_GPIO">RPI_GPIO</a> l&auml;sst sich dann ein get f&uuml;r den MCP23017 bei Pegel&auml;nderung ausl&ouml;sen.<br>
  589. I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  590. oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
  591. <b>Das Attribut IODev muss definiert sein.</b><br>
  592. <a name="I2C_MCP23017Define"></a><br>
  593. <b>Define</b>
  594. <ul>
  595. <code>define &lt;name&gt; I2C_MCP23017 &lt;I2C Address&gt;</code><br>
  596. Der Wert <code>&lt;I2C Address&gt;</code> ist ohne das Richtungsbit<br>
  597. </ul>
  598. <a name="I2C_MCP23017Set"></a>
  599. <b>Set</b>
  600. <ul>
  601. <code>set &lt;name&gt; &lt;port[,port[...]]&gt; &lt;value&gt;</code><br><br>
  602. <code>&lt;port&gt;</code> kann PortA0 bis PortA7 / PortB0 bis PortB7 annehmen und <code>&lt;value&gt;</code> folgende Werte:<br>
  603. <ul>
  604. <code>
  605. off<br>
  606. on<br>
  607. </code>
  608. </ul>
  609. <br>
  610. Beispiel:
  611. <ul>
  612. <code>set mod1 PortA4 on</code><br>
  613. <code>set mod1 PortA4,PortB6 off</code><br>
  614. <code>set mod1 PortA4,B6 on</code><br>
  615. </ul><br>
  616. </ul>
  617. <a name="I2C_MCP23017Get"></a>
  618. <b>Get</b>
  619. <ul>
  620. <code>get &lt;name&gt;</code>
  621. <br><br>
  622. Aktualisierung aller Werte
  623. </ul><br>
  624. <a name="I2C_MCP23017Attr"></a>
  625. <b>Attribute</b>
  626. <ul>
  627. <li>poll_interval<br>
  628. Aktualisierungsintervall aller Werte in Minuten.<br>
  629. Standard: -, g&uuml;ltige Werte: Dezimalzahl<br><br>
  630. </li>
  631. <li>OutputPorts<br>
  632. Durch Komma getrennte Ports die als Ausg&auml;nge genutzt werden sollen.<br>
  633. Nur Ports in dieser Liste k&ouml;nnen gesetzt werden.<br>
  634. Standard: -, g&uuml;ltige Werte: A0-A7, B0-B7<br><br>
  635. </li>
  636. <li>OnStartup<br>
  637. Durch Komma getrennte Output Ports und ihr gew&uuml;nschter Status nach dem Start.<br>
  638. Ohne dieses Attribut werden alle Ausg&auml;nge nach dem Start auf den letzten Status gesetzt.<br>
  639. Standard: -, g&uuml;ltige Werte: &lt;port&gt;=on|off|last wobei &lt;port&gt; = A0-A7, B0-B7<br><br>
  640. </li>
  641. <li>Pullup<br>
  642. Durch Komma getrennte Input Ports, bei denen der interne 100k pullup aktiviert werden soll.<br>
  643. Standard: -, g&uuml;ltige Werte: A0-A7, B0-B7<br><br>
  644. </li>
  645. <li>Interrupt<br>
  646. Durch Komma getrennte Input Ports, die einen Interrupt auf IntA/B ausl&ouml;sen.<br>
  647. Standard: -, g&uuml;ltige Werte: A0-A7, B0-B7<br><br>
  648. </li>
  649. <li>invert_input<br>
  650. Durch Komma getrennte Input Ports, die reverse Logik nutzen.<br>
  651. Standard: -, g&uuml;ltige Werte: A0-A7, B0-B7<br><br>
  652. </li>
  653. <li>InterruptOut<br>
  654. Einstellungen f&uuml;r die INTA/INTB Pins<br>
  655. g&uuml;ltige Werte:<br>
  656. <ul>
  657. <li>
  658. separate_active-low (INTA/INTB sind f&uuml;r PortA/PortB getrennt und mit active low Logik)
  659. </li>
  660. <li>
  661. separate_active-high (INTA/INTB sind f&uuml;r PortA/PortB getrennt und mit active high Logik)
  662. </li>
  663. <li>
  664. separate_open-drain (INTA/INTB sind f&uuml;r PortA/PortB getrennt und arbeiten als open drain)
  665. </li>
  666. <li>
  667. connected_active-low (INTA/INTB sind intern verbunden und mit active low Logik)
  668. </li>
  669. <li>
  670. connected_active-high (INTA/INTB sind intern verbunden und mit active high Logik)
  671. </li>
  672. <li>
  673. connected_open-drain (INTA/INTB sind intern verbunden und arbeiten als open drain)
  674. </li><br>
  675. </ul>
  676. </li>
  677. <li><a href="#IODev">IODev</a></li>
  678. <li><a href="#ignore">ignore</a></li>
  679. <li><a href="#do_not_notify">do_not_notify</a></li>
  680. <li><a href="#showtime">showtime</a></li>
  681. </ul>
  682. <br>
  683. </ul>
  684. =end html_DE
  685. =cut