52_I2C_DS1307.pm 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. ##############################################
  2. # $Id: 52_I2C_DS1307.pm 5927 2014-05-21 21:56:37Z ntruchsess $
  3. ##############################################
  4. package main;
  5. use strict;
  6. use warnings;
  7. #add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
  8. BEGIN {
  9. if ( !grep( /FHEM\/lib$/, @INC ) ) {
  10. foreach my $inc ( grep( /FHEM$/, @INC ) ) {
  11. push @INC, $inc . "/lib";
  12. }
  13. }
  14. }
  15. #####################################
  16. my %sets = (
  17. "datetime" => "",
  18. "now" => ""
  19. );
  20. my %gets = (
  21. "second" => "",
  22. "minute" => "",
  23. "hour" => "",
  24. "day" => "",
  25. "month" => "",
  26. "year" => "",
  27. "datetime" => "",
  28. "date" => "",
  29. "time" => ""
  30. );
  31. sub I2C_DS1307_Initialize($) {
  32. my ($hash) = @_;
  33. $hash->{DefFn} = "I2C_DS1307_Define";
  34. $hash->{InitFn} = "I2C_DS1307_Init";
  35. $hash->{SetFn} = "I2C_DS1307_Set";
  36. $hash->{AttrFn} = "I2C_DS1307_Attr";
  37. $hash->{I2CRecFn} = "I2C_DS1307_Receive";
  38. $hash->{AttrList} = "IODev poll_interval $main::readingFnAttributes";
  39. }
  40. sub I2C_DS1307_Define($$) {
  41. my ( $hash, $def ) = @_;
  42. my @a = split( "[ \t][ \t]*", $def );
  43. $hash->{STATE} = "defined";
  44. if ($main::init_done) {
  45. eval { I2C_DS1307_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  46. return I2C_DS1307_Catch($@) if $@;
  47. }
  48. return undef;
  49. }
  50. sub I2C_DS1307_Init($$) {
  51. my ( $hash, $args ) = @_;
  52. my $u =
  53. "wrong syntax: define <name> I2C_DS1307 [<address>]";
  54. return $u if (defined $args and int(@$args) > 1 );
  55. my $name = $hash->{NAME};
  56. my $address = defined $args ? shift @$args : 0b1101000; #default address
  57. $hash->{I2C_Address} = $address;
  58. if (! (defined AttrVal($name,"stateFormat",undef))) {
  59. $main::attr{$name}{"stateFormat"} = "datetime";
  60. }
  61. eval {
  62. main::AssignIoPort( $hash, AttrVal( $name, "IODev", undef ) );
  63. $hash->{DS1307} = Device::DS1307->new($address);
  64. $hash->{DS1307}->attach(I2C_DS1307_IO->new($hash));
  65. $hash->{STATE} = "Initialized";
  66. };
  67. return I2C_DS1307_Catch($@) if $@;
  68. I2C_DS1307_Poll($hash);
  69. return undef;
  70. }
  71. sub I2C_DS1307_Attr($$$$) {
  72. my ( $command, $name, $attribute, $value ) = @_;
  73. my $hash = $main::defs{$name};
  74. eval {
  75. if ( $command eq "set" )
  76. {
  77. ARGUMENT_HANDLER: {
  78. $attribute eq "IODev" and do {
  79. if ( $main::init_done and ( !defined( $hash->{IODev} ) or $hash->{IODev}->{NAME} ne $value ) )
  80. {
  81. main::AssignIoPort( $hash, $value );
  82. my @def = split( ' ', $hash->{DEF} ) if defined $hash->{DEF};
  83. I2C_DS1307_Init( $hash, \@def ) if ( defined( $hash->{IODev} ) );
  84. }
  85. last;
  86. };
  87. $attribute eq "poll_interval" and do {
  88. $hash->{POLL_INTERVAL} = $value;
  89. if ( $main::init_done )
  90. {
  91. I2C_DS1307_Poll($hash);
  92. }
  93. last;
  94. }
  95. }
  96. }
  97. };
  98. my $ret = I2C_DS1307_Catch($@) if $@;
  99. if ($ret) {
  100. $hash->{STATE} = "error setting $attribute to $value: " . $ret;
  101. return "cannot $command attribute $attribute to $value for $name: " . $ret;
  102. }
  103. }
  104. sub I2C_DS1307_Set(@) {
  105. my ( $hash, @a ) = @_;
  106. return "Need at least one parameters" if ( @a < 2 );
  107. shift @a;
  108. my $command = shift @a;
  109. if ( !defined( $sets{$command} ) ) {
  110. my @commands = ();
  111. foreach my $key ( sort keys %sets ) {
  112. push @commands,
  113. $sets{$key} ? $key . ":" . join( ",", $sets{$key} ) : $key;
  114. }
  115. return "Unknown argument $command, choose one of " . join( " ", @commands );
  116. }
  117. my $ds1307 = $hash->{DS1307};
  118. return unless defined $ds1307;
  119. eval {
  120. COMMAND_HANDLER: {
  121. $command eq "datetime" and do {
  122. $ds1307->setDatetime(join (' ',@a));
  123. main::readingsSingleUpdate( $hash, "datetime", $ds1307->getDatetime(), 1 );
  124. last;
  125. };
  126. $command eq "now" and do {
  127. $ds1307->setTime(time());
  128. main::readingsSingleUpdate( $hash, "datetime", $ds1307->getDatetime(), 1 );
  129. last;
  130. };
  131. }
  132. };
  133. return I2C_DS1307_Catch($@) if $@;
  134. return undef;
  135. }
  136. sub I2C_DS1307_Poll {
  137. my ( $hash ) = @_;
  138. RemoveInternalTimer($hash);
  139. eval {
  140. $hash->{DS1307}->read();
  141. };
  142. my $ret = I2C_DS1307_Catch($@) if $@;
  143. if ($ret) {
  144. $hash->{STATE} = "error reading DS1307: " . $ret;
  145. main::Log3 $hash->{NAME},4,"error reading DS1307: ".$ret;
  146. }
  147. InternalTimer(gettimeofday()+$hash->{POLL_INTERVAL}, 'I2C_DS1307_Poll', $hash, 0) if defined $hash->{POLL_INTERVAL};
  148. }
  149. # package:
  150. # i2caddress => $data->{address},
  151. # direction => "i2cread",
  152. # reg => $data->{register},
  153. # nbyte => scalar(@{$data->{data}}),
  154. # received => join (' ',@{$data->{data}})
  155. sub I2C_DS1307_Receive {
  156. my ( $hash, $package ) = @_;
  157. $hash->{DS1307}->receive(
  158. split (' ',$package->{received})
  159. );
  160. main::readingsSingleUpdate( $hash, "datetime", $hash->{DS1307}->getDatetime(), 1 );
  161. }
  162. sub I2C_DS1307_Catch($) {
  163. my $exception = shift;
  164. if ($exception) {
  165. $exception =~ /^(.*)( at.*FHEM.*)$/;
  166. return $1;
  167. }
  168. return undef;
  169. }
  170. package I2C_DS1307_IO;
  171. use strict;
  172. use warnings;
  173. sub new {
  174. my ( $class, $hash ) = @_;
  175. return bless { hash => $hash, }, $class;
  176. }
  177. sub i2c_write {
  178. my ( $self, $address, @data ) = @_;
  179. my $hash = $self->{hash};
  180. if ( defined( my $iodev = $hash->{IODev} ) ) {
  181. main::CallFn(
  182. $iodev->{NAME},
  183. "I2CWrtFn",
  184. $iodev,
  185. {
  186. i2caddress => $address,
  187. direction => "i2cwrite",
  188. data => join( ' ', @data ),
  189. }
  190. );
  191. }
  192. else {
  193. die "no IODev assigned to '$hash->{NAME}'";
  194. }
  195. }
  196. sub i2c_read {
  197. my ( $self, $address, $reg, $nbyte ) = @_;
  198. my $hash = $self->{hash};
  199. if ( defined( my $iodev = $hash->{IODev} ) ) {
  200. main::CallFn(
  201. $iodev->{NAME},
  202. "I2CWrtFn",
  203. $iodev,
  204. {
  205. i2caddress => $address,
  206. direction => "i2cread",
  207. reg => $reg,
  208. nbyte => $nbyte,
  209. }
  210. );
  211. }
  212. else {
  213. die "no IODev assigned to '$hash->{NAME}'";
  214. }
  215. }
  216. package Device::DS1307;
  217. use strict;
  218. use warnings;
  219. # DS1307 ADDRESS MAP
  220. use constant DS1307_SECONDS => 0x00;
  221. use constant DS1307_MINUTES => 0x01;
  222. use constant DS1307_HOURS => 0x02;
  223. use constant DS1307_DAY => 0x03;
  224. use constant DS1307_DATE => 0x04;
  225. use constant DS1307_MONTH => 0x05;
  226. use constant DS1307_YEAR => 0x06;
  227. use constant DS1307_CONTROL => 0x07;
  228. use constant DS1307_RAM => 0x08;
  229. # RAM 56 x 8 ?
  230. # ...
  231. # 0x3F
  232. # DS1307 Control Register:
  233. use constant DS1307_OUT => 0x40; # BIT 7 OUT
  234. use constant DS1307_SQWE => 0x10; # BIT 4 SQWE
  235. use constant DS1307_RS1 => 0x02; # BIT 1 RS1
  236. use constant DS1307_RS0 => 0x01; # BIT 0 RS0
  237. sub new {
  238. my ( $class, $address, $timezone, $century ) = @_;
  239. return bless {
  240. address => $address,
  241. time => time(),
  242. }, $class;
  243. }
  244. sub getDatetime {
  245. my ( $self ) = @_;
  246. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->{datetime});
  247. return sprintf "%04d-%02d-%02d %02d:%02d:%02d", ($year+1900,$mon+1,$mday,$hour,$min,$sec);
  248. }
  249. sub setDatetime {
  250. my ( $self, $value ) = @_;
  251. $self->{datetime} = main::time_str2num($value);
  252. $self->write();
  253. }
  254. sub setTime {
  255. my ( $self, $value ) = @_;
  256. $self->{datetime} = $value;
  257. $self->write();
  258. }
  259. sub attach {
  260. my ( $self, $io ) = @_;
  261. $self->{io} = $io;
  262. }
  263. sub read {
  264. my ( $self ) = @_;
  265. $self->{io}->i2c_read( $self->{address}, 0, 7 );
  266. }
  267. sub receive {
  268. my ($self, @data) = @_;
  269. my $sec = shift @data;
  270. my $min = shift @data;
  271. my $hour = shift @data;
  272. my $wday = shift @data;
  273. my $mday = shift @data;
  274. my $mon = shift @data;
  275. my $year = shift @data;
  276. #$self->{time} = mktime(sec, min, hour, mday, mon, year, wday = 0, yday = 0, isdst = -1)
  277. $self->{datetime} = main::mktime($sec, $min, $hour, $mday, $mon, $year, $wday, 0, -1);
  278. }
  279. sub write {
  280. my ( $self ) = @_;
  281. if (defined $self->{io}) {
  282. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->{datetime});
  283. $self->{io}->i2c_write(
  284. $self->{address}, #slave address
  285. 0, #register
  286. $sec, #data...
  287. $min,
  288. $hour,
  289. $wday, #DS1307 week starts on Sunday
  290. $mday,
  291. $mon,
  292. $year,
  293. 0); #control
  294. }
  295. };
  296. 1;
  297. =pod
  298. =begin html
  299. <a name="I2C_DS1307"></a>
  300. <h3>I2C_DS1307</h3>
  301. <ul>
  302. reads a DS1307 real-time clock chip via I2C.
  303. Requires a defined <a href="#I2C">I2C</a>-device to work.<br>
  304. <a name="I2C_DS1307define"></a>
  305. <b>Define</b>
  306. <ul>
  307. <code>define &lt;name&gt; I2C_DS1307 &lt;i2c-address&gt;</code> <br>
  308. Specifies the I2C_DS1307 device.<br>
  309. <li>i2c-address is the (device-specific) address of the ic on the i2c-bus</li>
  310. </ul>
  311. <br>
  312. <a name="I2C_DS1307set"></a>
  313. <b>Set</b><br>
  314. <ul>
  315. <li><code>set &lt;name&gt; datetime</code>; set DS1307 time. Format is JJJJ-MM-DD HH:MM:SSdisplayed&gt;<br></li>
  316. <li><code>set &lt;name&gt; now</code><br></li>
  317. </ul>
  318. <a name="I2C_I2Cget"></a>
  319. <b>Get</b><br>
  320. <ul>
  321. N/A<br>
  322. </ul><br>
  323. <a name="I2C_DS1307attr"></a>
  324. <b>Attributes</b><br>
  325. <ul>
  326. <li>poll_interval &lt;seconds&gt;</li>
  327. <li><a href="#IODev">IODev</a><br>
  328. Specify which <a href="#I2C">I2C</a> to use. (Optional, only required if there is more
  329. than one I2C-device defined.)
  330. </li>
  331. <li><a href="#eventMap">eventMap</a><br></li>
  332. <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
  333. </ul>
  334. </ul>
  335. <br>
  336. =end html
  337. =cut