OWX_DS2480.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. ########################################################################################
  2. #
  3. # OWX_DS2480.pm
  4. #
  5. # FHEM module providing hardware dependent functions for the DS2480 interface of OWX
  6. #
  7. # Prof. Dr. Peter A. Henning
  8. # Norbert Truchsess
  9. #
  10. # $Id: OWX_DS2480.pm 6398 2014-08-12 21:22:18Z ntruchsess $
  11. #
  12. ########################################################################################
  13. #
  14. # Provides the following methods for OWX
  15. #
  16. # Alarms
  17. # Complex
  18. # Define
  19. # Discover
  20. # Init
  21. # Reset
  22. # Verify
  23. #
  24. ########################################################################################
  25. package OWX_DS2480;
  26. use strict;
  27. use warnings;
  28. use Time::HiRes qw( gettimeofday tv_interval usleep );
  29. use ProtoThreads;
  30. no warnings 'deprecated';
  31. use vars qw/@ISA/;
  32. @ISA='OWX_SER';
  33. sub new($) {
  34. my ($class,$serial) = @_;
  35. return bless $serial,$class;
  36. }
  37. ########################################################################################
  38. #
  39. # Complex - Send match ROM, data block and receive bytes as response
  40. #
  41. # Parameter hash = hash of bus master,
  42. # owx_dev = ROM ID of device
  43. # data = string to send
  44. # numread = number of bytes to receive
  45. #
  46. # Return response, if OK
  47. # 0 if not OK
  48. #
  49. ########################################################################################
  50. sub get_pt_execute($$$$) {
  51. my ($self, $reset, $dev, $writedata, $numread) = @_;
  52. return PT_THREAD(sub {
  53. my ($thread) = @_;
  54. PT_BEGIN($thread);
  55. $self->reset() if ($reset);
  56. if (defined $writedata or $numread) {
  57. my $select;
  58. #-- has match ROM part
  59. if( $dev ) {
  60. #-- ID of the device
  61. my $owx_rnf = substr($dev,3,12);
  62. my $owx_f = substr($dev,0,2);
  63. #-- 8 byte 1-Wire device address
  64. my @rom_id =(0,0,0,0 ,0,0,0,0);
  65. #-- from search string to byte id
  66. $dev=~s/\.//g;
  67. for(my $i=0;$i<8;$i++){
  68. $rom_id[$i]=hex(substr($dev,2*$i,2));
  69. }
  70. $select=sprintf("\x55%c%c%c%c%c%c%c%c",@rom_id);
  71. #-- has no match ROM part, issue skip ROM command (0xCC:)
  72. } else {
  73. $select="\xCC";
  74. }
  75. if (defined $writedata) {
  76. $select.=$writedata;
  77. }
  78. #-- has receive data part
  79. if( $numread ) {
  80. #$numread += length($data);
  81. for( my $i=0;$i<$numread;$i++){
  82. $select .= "\xFF";
  83. };
  84. }
  85. #-- for debugging
  86. if( $main::owx_async_debug > 1){
  87. main::Log3($self->{name},5,"OWX_SER::Execute: Sending out ".unpack ("H*",$select));
  88. }
  89. $self->block($select);
  90. }
  91. main::OWX_ASYNC_TaskTimeout($self->{hash},gettimeofday+main::AttrVal($self->{name},"timeout",2));
  92. PT_WAIT_UNTIL($self->response_ready());
  93. if ($reset and !$self->reset_response()) {
  94. die "reset failure";
  95. }
  96. my $res = $self->{string_in};
  97. #-- for debugging
  98. if( $main::owx_async_debug > 1){
  99. main::Log3($self->{name},5,"OWX_SER::Execute: Receiving ".unpack ("H*",$res));
  100. }
  101. if (defined $res) {
  102. my $writelen = defined $writedata ? split (//,$writedata) : 0;
  103. my @result = split (//, $res);
  104. my $readdata = 9+$writelen < @result ? substr($res,9+$writelen) : "";
  105. PT_EXIT($readdata);
  106. }
  107. PT_END;
  108. });
  109. }
  110. ########################################################################################
  111. #
  112. # The following subroutines in alphabetical order are only for a DS2480 bus interface
  113. #
  114. #########################################################################################
  115. #
  116. # Block_2480 - Send data block (Fig. 6 of Maxim AN192)
  117. #
  118. # Parameter hash = hash of bus master, data = string to send
  119. #
  120. # Return response, if OK
  121. # 0 if not OK
  122. #
  123. ########################################################################################
  124. sub block ($) {
  125. my ($serial,$data) =@_;
  126. my $data2="";
  127. my $len = length($data);
  128. #-- if necessary, prepend E1 character for data mode
  129. if( substr($data,0,1) ne '\xE1') {
  130. $data2 = "\xE1";
  131. }
  132. #-- all E3 characters have to be duplicated
  133. for(my $i=0;$i<$len;$i++){
  134. my $newchar = substr($data,$i,1);
  135. $data2=$data2.$newchar;
  136. if( $newchar eq '\xE3'){
  137. $data2=$data2.$newchar;
  138. }
  139. }
  140. #-- write 1-Wire bus as a single string
  141. $serial->query($data2,$len);
  142. }
  143. ########################################################################################
  144. #
  145. # query - Write to the 1-Wire bus
  146. #
  147. # Parameter: cmd = string to send to the 1-Wire bus, retlen = expected len of the response
  148. #
  149. # Return: 1 when write was successful. undef otherwise
  150. #
  151. ########################################################################################
  152. sub query ($$$) {
  153. my ($serial,$cmd,$retlen) = @_;
  154. main::DevIo_SimpleWrite($serial->{hash},$cmd,0);
  155. $serial->{retlen} += $retlen;
  156. }
  157. ########################################################################################
  158. #
  159. # read - read response from the 1-Wire bus
  160. # to be called from OWX ReadFn.
  161. #
  162. # Parameter: -
  163. #
  164. # Return: 1 when at least 1 byte was read. undef otherwise
  165. #
  166. ########################################################################################
  167. sub read() {
  168. my ($serial) = @_;
  169. #-- read the data
  170. my $string_part = main::DevIo_DoSimpleRead($serial->{hash});
  171. $serial->{num_reads}++;
  172. #return undef unless defined $string_part;
  173. if (defined $string_part) {
  174. my $count_in = length ($string_part);
  175. $serial->{string_in} .= $string_part;
  176. $serial->{retcount} += $count_in;
  177. main::Log3($serial->{name},5, "OWX_DS2480 read: Loop no. $serial->{num_reads}, Receiving: ".unpack("H*",$string_part)) if( $main::owx_async_debug > 1 );
  178. return $count_in > 0 ? 1 : undef;
  179. } elsif ($main::owx_async_debug > 2) {
  180. main::Log3($serial->{name},5, "OWX_DS2480 read: Loop no. $serial->{num_reads}, no data read:");
  181. foreach my $i (0..6) {
  182. my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = caller($i);
  183. main::Log3($serial->{name},5, "$subroutine $filename $line");
  184. }
  185. }
  186. return undef;
  187. }
  188. sub response_ready() {
  189. my ($serial) = @_;
  190. if ($serial->{retcount} >= $serial->{retlen}) {
  191. main::Log3($serial->{name},5, "OWX_DS2480 read: After loop no. $serial->{num_reads} received: ".unpack("H*",$serial->{string_in}));
  192. return 1;
  193. }
  194. return 0;
  195. }
  196. sub start_query() {
  197. my ($serial) = @_;
  198. #read and discard any outstanding data from previous commands:
  199. while($serial->poll()) {};
  200. $serial->{string_in} = "";
  201. $serial->{num_reads} = 0;
  202. $serial->{retlen} = 0;
  203. $serial->{retcount} = 0;
  204. }
  205. ########################################################################################
  206. #
  207. # reset - Reset the 1-Wire bus (Fig. 4 of Maxim AN192)
  208. #
  209. # Parameter hash = hash of bus master
  210. #
  211. # Return 1 : OK
  212. # 0 : not OK
  213. #
  214. ########################################################################################
  215. sub reset() {
  216. my ($serial) = @_;
  217. my ($res,$r1,$r2);
  218. my $name = $serial->{name};
  219. $serial->start_query();
  220. #-- if necessary, prepend \xE3 character for command mode
  221. #-- Reset command \xC5
  222. #-- write 1-Wire bus
  223. $serial->query("\xE3\xC5",1);
  224. #-- sleeping for some time (value of 0.07 taken from original OWX_Query_DS2480)
  225. select(undef,undef,undef,0.07);
  226. }
  227. sub reset_response() {
  228. my ($serial) = @_;
  229. my $res = ord(substr($serial->{string_in},0,1));
  230. my $name = $serial->{name};
  231. if( !($res & 192) ) {
  232. main::Log3($name,4, "OWX_DS2480 reset failure on bus $name");
  233. return 0;
  234. }
  235. if( ($res & 3) == 2 ) {
  236. main::Log3($name,4, "OWX_DS2480 reset Alarm presence detected on bus $name");
  237. $serial->{ALARMED} = "yes";
  238. } else {
  239. $serial->{ALARMED} = "no";
  240. }
  241. $serial->{string_in} = substr($serial->{string_in},1);
  242. return 1;
  243. }
  244. ########################################################################################
  245. #
  246. # Search_2480 - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
  247. # search state.
  248. #
  249. # Parameter hash = hash of bus master, mode=alarm,discover or verify
  250. #
  251. # Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0)
  252. # or only in owx_ROM_ID (LastDeviceFlag=1)
  253. # 0 : device not found, or ot searched at all
  254. #
  255. ########################################################################################
  256. sub pt_next ($$) {
  257. my ($serial,$context,$mode)=@_;
  258. my $id_bit_number = 1;
  259. my $rom_byte_number = 0;
  260. my $rom_byte_mask = 1;
  261. my $last_zero = 0;
  262. my ($sp1,$sp2,$pt_query,$search_direction);
  263. return PT_THREAD(sub {
  264. my ( $thread ) = @_;
  265. PT_BEGIN($thread);
  266. #-- clear 16 byte of search data
  267. $context->{search} = [0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0];
  268. #-- Output search data construction (Fig. 9 of Maxim AN192)
  269. # operates on a 16 byte search response = 64 pairs of two bits
  270. while ( $id_bit_number <= 64) {
  271. #-- address single bits in a 16 byte search string
  272. my $newcpos = int(($id_bit_number-1)/4);
  273. my $newimsk = ($id_bit_number-1)%4;
  274. #-- address single bits in a 8 byte id string
  275. my $newcpos2 = int(($id_bit_number-1)/8);
  276. my $newimsk2 = ($id_bit_number-1)%8;
  277. if( $id_bit_number <= $context->{LastDiscrepancy}){
  278. #-- first use the ROM ID bit to set the search direction
  279. if( $id_bit_number < $context->{LastDiscrepancy} ) {
  280. $search_direction = ($context->{ROM_ID}->[$newcpos2]>>$newimsk2) & 1;
  281. #-- at the last discrepancy search into 1 direction anyhow
  282. } else {
  283. $search_direction = 1;
  284. }
  285. #-- fill into search data;
  286. $context->{search}->[$newcpos]+=$search_direction<<(2*$newimsk+1);
  287. }
  288. #--increment number
  289. $id_bit_number++;
  290. }
  291. #-- issue data mode \xE1, the normal search command \xF0 or the alarm search command \xEC
  292. # and the command mode \xE3 / start accelerator \xB5
  293. if( $mode ne "alarm" ){
  294. $sp1 = "\xE1\xF0\xE3\xB5";
  295. } else {
  296. $sp1 = "\xE1\xEC\xE3\xB5";
  297. }
  298. #-- issue data mode \xE1, device ID, command mode \xE3 / end accelerator \xA5
  299. $sp2=sprintf("\xE1%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\xE3\xA5",@{$context->{search}});
  300. $serial->reset();
  301. $serial->query($sp1,1);
  302. $serial->query($sp2,16);
  303. main::OWX_ASYNC_TaskTimeout($serial->{hash},gettimeofday+main::AttrVal($serial->{name},"timeout",2));
  304. PT_WAIT_UNTIL($serial->response_ready());
  305. die "reset failed" unless $serial->reset_response();
  306. my $response = substr($serial->{string_in},1);
  307. #-- interpret the return data
  308. if( length($response)!=16 ) {
  309. die "OWX_DS2480: Search 2nd return has wrong parameter with length = ".(length($response)."");
  310. }
  311. #-- Response search data parsing (Fig. 11 of Maxim AN192)
  312. # operates on a 16 byte search response = 64 pairs of two bits
  313. $id_bit_number = 1;
  314. #-- clear 8 byte of device id for current search
  315. $context->{ROM_ID} = [0,0,0,0 ,0,0,0,0];
  316. while ( $id_bit_number <= 64) {
  317. #-- adress single bits in a 16 byte string
  318. my $newcpos = int(($id_bit_number-1)/4);
  319. my $newimsk = ($id_bit_number-1)%4;
  320. #-- retrieve the new ROM_ID bit
  321. my $newchar = substr($response,$newcpos,1);
  322. #-- these are the new bits
  323. my $newibit = (( ord($newchar) >> (2*$newimsk) ) & 2) / 2;
  324. my $newdbit = ( ord($newchar) >> (2*$newimsk) ) & 1;
  325. #-- discrepancy=1 and ROM_ID=0
  326. if( ($newdbit==1) and ($newibit==0) ){
  327. $context->{LastDiscrepancy}=$id_bit_number;
  328. if( $id_bit_number < 9 ){
  329. $context->{LastFamilyDiscrepancy}=$id_bit_number;
  330. }
  331. }
  332. #-- fill into device data; one char per 8 bits
  333. $context->{ROM_ID}->[int(($id_bit_number-1)/8)]+=$newibit<<(($id_bit_number-1)%8);
  334. #-- increment number
  335. $id_bit_number++;
  336. main::Log3 ($serial->{name},5,"id_bit_number: $id_bit_number, LastDiscrepancy: $context->{LastDiscrepancy} ROM_ID: ".sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@{$context->{ROM_ID}})) if ($main::owx_async_debug > 2);
  337. }
  338. PT_END;
  339. });
  340. }
  341. 1;