OWX_SER.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. ########################################################################################
  2. #
  3. # OWX_SER.pm
  4. #
  5. # FHEM module providing hardware dependent functions for the serial (USB) interface of OWX
  6. #
  7. # Prof. Dr. Peter A. Henning
  8. # Norbert Truchsess
  9. #
  10. # $Id: OWX_SER.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_SER;
  26. use strict;
  27. use warnings;
  28. use vars qw/@ISA/;
  29. require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
  30. use Time::HiRes qw( gettimeofday );
  31. use ProtoThreads;
  32. no warnings 'deprecated';
  33. ########################################################################################
  34. #
  35. # Constructor
  36. #
  37. ########################################################################################
  38. sub new() {
  39. my $class = shift;
  40. my $self = {
  41. interface => "serial",
  42. #-- module version
  43. version => 5.2,
  44. alarmdevs => [],
  45. devs => [],
  46. fams => [],
  47. };
  48. return bless $self,$class;
  49. }
  50. sub poll() {
  51. my ( $self ) = @_;
  52. my $hash = $self->{hash};
  53. if(defined($hash->{FD})) {
  54. my ($rout, $rin) = ('', '');
  55. vec($rin, $hash->{FD}, 1) = 1;
  56. select($rout=$rin, undef, undef, 0.1);
  57. my $mfound = vec($rout, $hash->{FD}, 1);
  58. if ($mfound) {
  59. if ($self->read()) {
  60. return 1;
  61. } else {
  62. main::OWX_ASYNC_Disconnect($hash);
  63. }
  64. }
  65. }
  66. return undef;
  67. }
  68. ########################################################################################
  69. #
  70. # Public methods
  71. #
  72. ########################################################################################
  73. #
  74. # Define - Implements Define method
  75. #
  76. # Parameter def = definition string
  77. #
  78. # Return undef if ok, otherwise error message
  79. #
  80. ########################################################################################
  81. sub Define ($$) {
  82. my ($self,$hash,$def) = @_;
  83. my @a = split("[ \t][ \t]*", $def);
  84. $self->{name} = $hash->{NAME};
  85. #-- check syntax
  86. if(int(@a) < 3){
  87. return "OWX_SER: Syntax error - must be define <name> OWX <serial-device>"
  88. }
  89. my $dev = $a[2];
  90. my $device;
  91. #-- network attached serial:
  92. if ( $dev =~ m/^(.+):([0-9]+)$/ ) {
  93. $hash->{DeviceName} = $dev;
  94. $device = $dev;
  95. } else {
  96. #-- when the specified device name contains @<digits> already, use it as supplied
  97. if ( $dev !~ m/\@\d*/ ){
  98. $hash->{DeviceName} = $dev."\@9600";
  99. }
  100. my $baudrate;
  101. ($device,$baudrate) = split('@',$dev);
  102. $self->{baud} = $baudrate ? $baudrate : 9600;
  103. }
  104. #-- let fhem.pl MAIN call OWX_Ready when setup is done.
  105. $main::readyfnlist{"$hash->{NAME}.$device"} = $hash;
  106. $self->{hash} = $hash;
  107. return undef;
  108. }
  109. ########################################################################################
  110. #
  111. # Alarms - Find devices on the 1-Wire bus, which have the alarm flag set
  112. #
  113. # Return number of alarmed devices
  114. #
  115. ########################################################################################
  116. sub get_pt_alarms() {
  117. my ($self) = @_;
  118. my $pt_next;
  119. return PT_THREAD(sub {
  120. my ($thread) = @_;
  121. PT_BEGIN($thread);
  122. $thread->{alarmdevs} = [];
  123. #-- Discover all alarmed devices on the 1-Wire bus
  124. $self->first($thread);
  125. do {
  126. $pt_next = $self->pt_next($thread,"alarms");
  127. PT_WAIT_THREAD($pt_next);
  128. die $pt_next->PT_CAUSE() if ($pt_next->PT_STATE() == PT_ERROR || $pt_next->PT_STATE() == PT_CANCELED);
  129. $self->next_response($thread,"alarms");
  130. } while( $thread->{LastDeviceFlag}==0 );
  131. main::Log3($self->{name},5, " Alarms = ".join(' ',@{$thread->{alarmdevs}}));
  132. PT_EXIT($thread->{alarmdevs});
  133. PT_END;
  134. });
  135. }
  136. ########################################################################################
  137. #
  138. # Discover - Find devices on the 1-Wire bus
  139. #
  140. # Parameter hash = hash of bus master
  141. #
  142. # Return 1, if alarmed devices found, 0 otherwise.
  143. #
  144. ########################################################################################
  145. sub get_pt_discover() {
  146. my ($self) = @_;
  147. my $pt_next;
  148. return PT_THREAD(sub {
  149. my ($thread) = @_;
  150. PT_BEGIN($thread);
  151. #-- Discover all alarmed devices on the 1-Wire bus
  152. $self->first($thread);
  153. do {
  154. $pt_next = $self->pt_next($thread,"discover");
  155. PT_WAIT_THREAD($pt_next);
  156. die $pt_next->PT_CAUSE() if ($pt_next->PT_STATE() == PT_ERROR || $pt_next->PT_STATE() == PT_CANCELED);
  157. $self->next_response($thread,"discover");
  158. } while( $thread->{LastDeviceFlag}==0 );
  159. PT_EXIT($thread->{devs});
  160. PT_END;
  161. });
  162. }
  163. ########################################################################################
  164. #
  165. # Init - Initialize the 1-wire device
  166. #
  167. # Parameter hash = hash of bus master
  168. #
  169. # Return 1 or Errormessage : not OK
  170. # 0 or undef : OK
  171. #
  172. ########################################################################################
  173. sub initialize() {
  174. my ($self) = @_;
  175. my ($i,$j,$k,$l,$res,$ret,$ress);
  176. #-- Second step in case of serial device: open the serial device to test it
  177. my $hash = $self->{hash};
  178. my $msg = "OWX_SER: Serial device $hash->{DeviceName}";
  179. main::DevIo_OpenDev($hash,$self->{reopen},undef);
  180. return undef unless $hash->{STATE} eq "opened";
  181. #-- Third step detect busmaster on serial interface
  182. my $name = $self->{name};
  183. my $ress0 = "OWX_SER::Detect 1-Wire bus $name: interface ";
  184. $ress = $ress0;
  185. my $interface;
  186. require "$main::attr{global}{modpath}/FHEM/OWX_DS2480.pm";
  187. my $ds2480 = OWX_DS2480->new($self);
  188. if (defined (my $hwdevice = $hash->{USBDev})) {
  189. #force master reset in DS2480
  190. $hwdevice->reset_error();
  191. $hwdevice->purge_all;
  192. $hwdevice->baudrate(4800);
  193. $hwdevice->write_settings;
  194. $hwdevice->write("\x00");
  195. select(undef,undef,undef,0.5);
  196. #-- timing byte for DS2480
  197. $ds2480->start_query();
  198. $hwdevice->baudrate(9600);
  199. $hwdevice->write_settings;
  200. $ds2480->query("\xC1",0);
  201. $hwdevice->baudrate($self->{baud});
  202. $hwdevice->write_settings;
  203. } else {
  204. #-- for serial over network we cannot reset but just send the timing byte
  205. $ds2480->start_query();
  206. $ds2480->query("\xC1",0);
  207. }
  208. #-- Max 4 tries to detect an interface
  209. for($l=0;$l<100;$l++) {
  210. #-- write 1-Wire bus (Fig. 2 of Maxim AN192)
  211. $ds2480->start_query();
  212. $ds2480->query("\x17\x45\x5B\x0F\x91",5);
  213. my $until = gettimeofday + main::AttrVal($hash->{NAME},"timeout",1);
  214. eval {
  215. do {
  216. $ds2480->poll();
  217. } until ($ds2480->response_ready() or gettimeofday >= $until);
  218. };
  219. $res = $ds2480->{string_in};
  220. #-- process 4/5-byte string for detection
  221. if( !defined($res)){
  222. $res="";
  223. $ret=1;
  224. }elsif( ($res eq "\x16\x44\x5A\x00\x90") || ($res eq "\x16\x44\x5A\x00\x93")){
  225. $ress .= "master DS2480 detected for the first time";
  226. $interface="DS2480";
  227. $ret=0;
  228. } elsif( $res eq "\x17\x45\x5B\x0F\x91"){
  229. $ress .= "master DS2480 re-detected";
  230. $interface="DS2480";
  231. $ret=0;
  232. } elsif( ($res eq "\x17\x0A\x5B\x0F\x02") || ($res eq "\x00\x17\x0A\x5B\x0F\x02") || ($res eq "\x30\xf8\x00") || ($res eq "\x06\x00\x09\x07\x80") || ($res eq "\x17\x41\xAB\x20\xFC")){
  233. $ress .= "passive DS9097 detected";
  234. $interface="DS9097";
  235. $ret=0;
  236. } else {
  237. $ret=1;
  238. }
  239. last
  240. if( $ret==0 );
  241. $ress .= "not found, answer was ";
  242. for($i=0;$i<length($res);$i++){
  243. $j=int(ord(substr($res,$i,1))/16);
  244. $k=ord(substr($res,$i,1))%16;
  245. $ress.=sprintf "0x%1x%1x ",$j,$k;
  246. }
  247. main::Log3($hash->{NAME},4, $ress);
  248. $ress = $ress0;
  249. #-- sleeping for some time
  250. select(undef,undef,undef,0.5);
  251. }
  252. if( $ret == 1 ){
  253. $interface=undef;
  254. $ress .= "not detected, answer was ";
  255. for($i=0;$i<length($res);$i++){
  256. $j=int(ord(substr($res,$i,1))/16);
  257. $k=ord(substr($res,$i,1))%16;
  258. $ress.=sprintf "0x%1x%1x ",$j,$k;
  259. }
  260. }
  261. $self->{interface} = $interface;
  262. main::Log3($hash->{NAME},3, $ress);
  263. if ($interface eq "DS2480") {
  264. return $ds2480;
  265. } elsif ($interface eq "DS9097") {
  266. require "$main::attr{global}{modpath}/FHEM/OWX_DS9097.pm";
  267. return OWX_DS9097->new($self);
  268. } else {
  269. die $ress;
  270. }
  271. }
  272. sub exit($) {
  273. my ($self) = @_;
  274. main::DevIo_Disconnected($self->{hash});
  275. $self->{interface} = "serial";
  276. $self->{reopen} = 1;
  277. }
  278. ########################################################################################
  279. #
  280. # Verify - Verify a particular device on the 1-Wire bus
  281. #
  282. # Parameter hash = hash of bus master, dev = 8 Byte ROM ID of device to be tested
  283. #
  284. # Return 1 : device found
  285. # 0 : device not
  286. #
  287. ########################################################################################
  288. sub get_pt_verify($) {
  289. my ($self,$dev) = @_;
  290. my $pt_next;
  291. return PT_THREAD(sub {
  292. my ($thread) = @_;
  293. my $i;
  294. PT_BEGIN($thread);
  295. #-- from search string to byte id
  296. my $devs=$dev;
  297. $devs=~s/\.//g;
  298. for($i=0;$i<8;$i++){
  299. $thread->{ROM_ID}->[$i]=hex(substr($devs,2*$i,2));
  300. }
  301. #-- reset the search state
  302. $thread->{LastDiscrepancy} = 65;
  303. $thread->{LastDeviceFlag} = 0;
  304. $thread->{LastFamilyDiscrepancy} = 0;
  305. #-- now do the search
  306. $pt_next = $self->pt_next($thread,"verify");
  307. PT_WAIT_THREAD($pt_next);
  308. die $pt_next->PT_CAUSE() if ($pt_next->PT_STATE() == PT_ERROR || $pt_next->PT_STATE() == PT_CANCELED);
  309. $self->next_response($thread,"verify");
  310. my $dev2=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@{$thread->{ROM_ID}});
  311. #-- reset the search state
  312. $thread->{LastDiscrepancy} = 0;
  313. $thread->{LastDeviceFlag} = 0;
  314. $thread->{LastFamilyDiscrepancy} = 0;
  315. #-- check result
  316. if ($dev eq $dev2){
  317. PT_EXIT(1);
  318. }else{
  319. PT_EXIT(0);
  320. }
  321. PT_END;
  322. });
  323. };
  324. #######################################################################################
  325. #
  326. # First - Find the 'first' devices on the 1-Wire bus
  327. #
  328. # Parameter hash = hash of bus master, mode
  329. #
  330. # Return 1 : device found, ROM number pushed to list
  331. # 0 : no device present
  332. #
  333. ########################################################################################
  334. sub first($) {
  335. my ($self,$thread) = @_;
  336. #-- reset the search state
  337. $thread->{LastDiscrepancy} = 0;
  338. $thread->{LastDeviceFlag} = 0;
  339. $thread->{LastFamilyDiscrepancy} = 0;
  340. $thread->{ROM_ID} = [0,0,0,0,0,0,0,0];
  341. }
  342. sub next_response($) {
  343. my ($self,$thread,$mode) = @_;
  344. #-- character version of device ROM_ID, first byte = family
  345. my $dev=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@{$thread->{ROM_ID}});
  346. #-- mode was to verify presence of a device
  347. if ($mode eq "verify") {
  348. main::Log3($self->{name},5, "OWX_SER::Search: device verified $dev");
  349. #--check if we really found a device
  350. } elsif( main::OWX_CRC($thread->{ROM_ID})!= 0) {
  351. #-- reset the search
  352. main::Log3($self->{name},1, "OWX_SER::Search CRC failed : $dev");
  353. $thread->{LastDiscrepancy} = 0;
  354. $thread->{LastDeviceFlag} = 0;
  355. $thread->{LastFamilyDiscrepancy} = 0;
  356. die "OWX_SER::Search CRC failed : $dev";
  357. }
  358. #--
  359. if( $thread->{LastDiscrepancy}==$thread->{LastFamilyDiscrepancy} ){
  360. $thread->{LastFamilyDiscrepancy}=0;
  361. }
  362. #-- mode was to discover devices
  363. if( $mode eq "discover" ) {
  364. #-- check families
  365. my $famfnd=0;
  366. foreach (@{$self->{fams}}){
  367. if( substr($dev,0,2) eq $_ ){
  368. #-- if present, set the fam found flag
  369. $famfnd=1;
  370. last;
  371. }
  372. }
  373. push(@{$self->{fams}},substr($dev,0,2)) if( !$famfnd );
  374. foreach (@{$thread->{devs}}){
  375. if( $dev eq $_ ){
  376. #-- if present, set the last device found flag
  377. $thread->{LastDeviceFlag}=1;
  378. last;
  379. }
  380. }
  381. if( $thread->{LastDeviceFlag}!=1 ){
  382. #-- push to list
  383. push(@{$thread->{devs}},$dev);
  384. main::Log3($self->{name},5, "OWX_SER::Search: new device found $dev");
  385. }
  386. #-- mode was to discover alarm devices
  387. } elsif ( $mode eq "alarms" ) {
  388. for(my $i=0;$i<@{$thread->{alarmdevs}};$i++){
  389. if( $dev eq ${$thread->{alarmdevs}}[$i] ){
  390. #-- if present, set the last device found flag
  391. $thread->{LastDeviceFlag}=1;
  392. last;
  393. }
  394. }
  395. if( $thread->{LastDeviceFlag}!=1 ){
  396. #--push to list
  397. push(@{$thread->{alarmdevs}},$dev);
  398. main::Log3($self->{name},5, "OWX_SER::Search: new alarm device found $dev");
  399. }
  400. }
  401. return 1;
  402. }
  403. 1;