PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/v2-29/mh/lib/Serial_Item.pm

#
Perl | 512 lines | 303 code | 43 blank | 166 comment | 62 complexity | 4db9719db7453181e12b022cce27e020 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, GPL-3.0
  1. use strict;
  2. package Serial_Item;
  3. @Serial_Item::ISA = ('Generic_Item');
  4. my (%serial_items_by_id);
  5. sub reset {
  6. undef %serial_items_by_id; # Reset on code re-load
  7. }
  8. sub serial_items_by_id {
  9. my($id) = @_;
  10. return unless $serial_items_by_id{$id};
  11. return @{$serial_items_by_id{$id}};
  12. }
  13. # For backward compatability, return just the first item
  14. sub serial_item_by_id {
  15. my($id) = @_;
  16. my @refs = &serial_items_by_id($id);
  17. return $refs[0];
  18. }
  19. sub new {
  20. my ($class, $id, $state, $port_name) = @_;
  21. my $self = {state => ''};
  22. # print "\n\nWarning: duplicate ID codes on different Serial_Item objects:\n " .
  23. # "id=$id state=$state states=@{${$serial_item_by_id{$id}}{states}}\n\n" if $serial_item_by_id{$id};
  24. $$self{port_name} = $port_name;
  25. &add($self, $id, $state);
  26. bless $self, $class;
  27. $self->set_interface($port_name) if $id and $id =~ /^X/;
  28. return $self;
  29. }
  30. sub add {
  31. my ($self, $id, $state) = @_;
  32. # Allow for Serial_Item's without states
  33. $state = 'default_state' unless defined $state;
  34. $$self{state_by_id}{$id} = $state if $id;
  35. $$self{id_by_state}{$state} = $id;
  36. push(@{$$self{states}}, $state);
  37. push(@{$serial_items_by_id{$id}}, $self) if $id;
  38. }
  39. sub is_started {
  40. my ($self) = @_;
  41. my $port_name = $self->{port_name};
  42. return ($main::Serial_Ports{$port_name}{object}) ? 1 : 0;
  43. }
  44. sub is_stopped {
  45. my ($self) = @_;
  46. my $port_name = $self->{port_name};
  47. return ($main::Serial_Ports{$port_name}{object}) ? 0 : 1;
  48. }
  49. # Try to do a 'new' ... object is not kept, even if new is sucessful
  50. # - not sure if there is a better way to test if a port is available
  51. # Hopefully this is not too wasteful
  52. sub is_available {
  53. my ($self) = @_;
  54. my $port_name = $self->{port_name};
  55. my $port = $main::Serial_Ports{$port_name}{port};
  56. print "testing port $port ... ";
  57. my $sp_object;
  58. # Use the 2nd parm of '1' to indicate this do a test open
  59. # - Modified Win32::SerialPort so it does not compilain if New/open fails
  60. if (( $main::OS_win and $sp_object = new Win32::SerialPort($port, 1))or
  61. (!$main::OS_win and $sp_object = new Device::SerialPort($port))) {
  62. print " available\n";
  63. $sp_object->close;
  64. return 1;
  65. }
  66. else {
  67. print " not available\n";
  68. return 0;
  69. }
  70. }
  71. sub start {
  72. my ($self) = @_;
  73. my $port_name = $self->{port_name};
  74. print "Starting port $port_name on port $main::Serial_Ports{$port_name}{port}\n";
  75. if ($main::Serial_Ports{$port_name}{object}) {
  76. print "Port $port_name is already started\n";
  77. return;
  78. }
  79. if ($port_name) {
  80. if (&main::serial_port_open($port_name)) {
  81. print "Port $port_name was re-opened\n";
  82. }
  83. else {
  84. print "Serial_Item start failed for port $port_name\n";
  85. }
  86. }
  87. else {
  88. print "Error in Serial_Item start: no port name for object=$self\n";
  89. }
  90. }
  91. sub stop {
  92. my ($self) = @_;
  93. my $port_name = $self->{port_name};
  94. my $sp_object = $main::Serial_Ports{$port_name}{object};
  95. if ($sp_object) {
  96. my $port = $main::Serial_Ports{$port_name}{port};
  97. # &Win32::SerialPort::debug(1);
  98. if ($sp_object->close) {
  99. print "Port $port_name on port $port was closed\n";
  100. }
  101. else {
  102. print "Serial_Item stop failed for port $port_name\n";
  103. }
  104. # Delete the ports, even if it didn't close, so we can do
  105. # starts again without a 'port reuse' message.
  106. delete $main::Serial_Ports{$port_name}{object};
  107. delete $main::Serial_Ports{object_by_port}{$port};
  108. # &Win32::SerialPort::debug(0);
  109. }
  110. else {
  111. print "Error in Serial_Item stop for port $port_name: Port is not started\n";
  112. }
  113. }
  114. sub said {
  115. my $port_name = $_[0]->{port_name};
  116. my $datatype = $main::Serial_Ports{$port_name}{datatype};
  117. my $data;
  118. if ($datatype and $datatype eq 'raw') {
  119. $data = $main::Serial_Ports{$port_name}{data};
  120. $main::Serial_Ports{$port_name}{data} = '';
  121. }
  122. else {
  123. $data = $main::Serial_Ports{$port_name}{data_record};
  124. $main::Serial_Ports{$port_name}{data_record} = ''; # Maybe this should be reset in main loop??
  125. }
  126. return $data;
  127. }
  128. sub set_data {
  129. my ($self, $data) = @_;
  130. my $port_name = $self->{port_name};
  131. my $datatype = $main::Serial_Ports{$port_name}{datatype};
  132. if ($datatype eq 'raw') {
  133. $main::Serial_Ports{$port_name}{data} = $data;
  134. }
  135. else {
  136. $main::Serial_Ports{$port_name}{data_record} = $data;
  137. }
  138. }
  139. sub set_receive {
  140. my ($self, $state) = @_;
  141. &Generic_Item::set_states_for_next_pass($self, $state);
  142. }
  143. sub set_dtr {
  144. my ($self, $state) = @_;
  145. my $port_name = $self->{port_name};
  146. if (my $serial_port = $main::Serial_Ports{$port_name}{object}) {
  147. $main::Serial_Ports{$port_name}{object}->dtr_active($state);
  148. print "Serial_port $port_name dtr set to $state\n" if $main::config_parms{debug} eq 'serial';
  149. }
  150. else {
  151. print "Error, serial port set_dtr for $port_name failed, port has not been set\n";
  152. }
  153. }
  154. sub set_rts {
  155. my ($self, $state) = @_;
  156. my $port_name = $self->{port_name};
  157. if (my $serial_port = $main::Serial_Ports{$port_name}{object}) {
  158. $main::Serial_Ports{$port_name}{object}->rts_active($state);
  159. print "Serial_port $port_name rts set to $state\n" if $main::config_parms{debug} eq 'serial';
  160. }
  161. else {
  162. print "Error, serial port set_rts for $port_name failed, port has not been set\n";
  163. }
  164. }
  165. sub set {
  166. my ($self, $state) = @_;
  167. # Allow for Serial_Item's without states
  168. $state = 'default_state' unless defined $state;
  169. &Generic_Item::set_states_for_next_pass($self, $state);
  170. return unless %main::Serial_Ports;
  171. my $serial_id;
  172. if (defined $self->{id_by_state}{$state}) {
  173. $serial_id = $self->{id_by_state}{$state};
  174. }
  175. else {
  176. $serial_id = $state;
  177. }
  178. my $serial_data = $serial_id;
  179. my $port_name = $self->{port_name};
  180. print "Serial_Item: port=$port_name self=$self state=$state data=$serial_data interface=$$self{interface}\n"
  181. if $main::config_parms{debug} eq 'serial';
  182. return if $main::Save{mode} eq 'offline';
  183. my $interface = $$self{interface};
  184. $interface = 'none' unless $interface;
  185. if ($interface eq 'cm11') {
  186. # Allow for xx% (e.g. 1% -> &P1)
  187. if ($serial_data =~ /(\d+)%/) {
  188. $serial_data = '&P' . int ($1 * 63 / 100 + 0.5);
  189. }
  190. # Make sure that &P codes have the house code prefixed
  191. # - e.g. device A1 -> A&P1
  192. if ($serial_data =~ /^&P/) {
  193. $serial_data = substr($self->{x10_id}, 1, 1) . $serial_data;
  194. }
  195. # If code is &P##, prefix with item code.
  196. # - e.g. A&P1 -> A1A&P1
  197. if (substr($serial_data, 1, 1) eq '&') {
  198. $serial_data = $self->{x10_id} . $serial_data;
  199. }
  200. # Allow for:
  201. # - Extended data will have & starting the function code
  202. # e.g. XO7&P23 -> Device O7 to Preset Dim code 23
  203. # - Bright/dim on house codes: e.g. XA+20 (but not XA1A+20)
  204. # if (length $serial_data > 3 and substr($serial_data, 3, 1) eq '&' or
  205. # $serial_data =~ /^\S\S[\+\-]/) {
  206. # &ControlX10::CM11::send($main::Serial_Ports{cm11}{object}, substr($serial_data, 1));
  207. # }
  208. # Normal data ... call once for the Unit code, once for the Function code
  209. # else {
  210. &ControlX10::CM11::send($main::Serial_Ports{cm11}{object}, substr($serial_data, 1, 2));
  211. &ControlX10::CM11::send($main::Serial_Ports{cm11}{object}, substr($serial_data, 3)) if length($serial_data) > 3;
  212. # }
  213. }
  214. elsif ($interface eq 'cm17') {
  215. # cm17 wants XA1K, not XA1AK
  216. substr($serial_data, 3, 1) = '';
  217. &ControlX10::CM17::send($main::Serial_Ports{cm17}{object}, substr($serial_data, 1));
  218. }
  219. elsif ($interface eq 'homevision') {
  220. print "Using homevision to send: $serial_data\n";
  221. &Homevision::send($main::Serial_Ports{Homevision}{object}, $serial_data);
  222. }
  223. elsif ($interface eq 'homebase') {
  224. print "Using homebase to send: $serial_data\n";
  225. &HomeBase::send_X10($main::Serial_Ports{HomeBase}{object}, substr($serial_data, 1, 2));
  226. &HomeBase::send_X10($main::Serial_Ports{HomeBase}{object}, substr($serial_data, 3)) if length($serial_data) > 2;
  227. }
  228. elsif ($interface eq 'houselinc') {
  229. print "Using houselinc to send: $serial_data\n";
  230. &HouseLinc::send_X10($main::Serial_Ports{HouseLinc}{object}, $serial_data);
  231. }
  232. elsif ($interface eq 'marrick') {
  233. print "Using marrick to send: $serial_data\n";
  234. &Marrick::send_X10($main::Serial_Ports{Marrick}{object}, $serial_data);
  235. }
  236. elsif ($interface eq 'ncpuxa') {
  237. print "Using ncpuxa to send: $serial_data\n";
  238. &ncpuxa_mh::send($main::config_parms{ncpuxa_port}, $serial_data);
  239. }
  240. else {
  241. $port_name = 'Homevision' if !$port_name and $main::Serial_Ports{Homevision}{object}; #Since it's multifunction, it should be default
  242. $port_name = 'weeder' if !$port_name and $main::Serial_Ports{weeder}{object};
  243. $port_name = 'serial1' if !$port_name and $main::Serial_Ports{serial1}{object};
  244. $port_name = 'serial2' if !$port_name and $main::Serial_Ports{serial2}{object};
  245. # print "\$port_name is $port_name\n\$main::Serial_Ports{Homevision}{object} is $main::Serial_Ports{Homevision}{object}\n";
  246. unless ($port_name) {
  247. print "Error, serial set called, but no serial port found: data=$serial_data\n";
  248. return;
  249. }
  250. unless ($main::Serial_Ports{$port_name}{object}) {
  251. print "Error, serial port for $port_name has not been set: data=$serial_data\n";
  252. return;
  253. }
  254. # Weeder table does not match what we defined in CM11,CM17,X10_Items.pm
  255. # - Dim -> L, Bright -> M, AllOn -> I, AllOff -> H
  256. if ($port_name eq 'weeder' and
  257. my ($device, $house, $command) = $serial_data =~ /^X(\S\S)(\S)(\S+)/) {
  258. # Allow for +-xx%
  259. my $dim_amount = 3;
  260. if ($command =~ /[\+\-]\d+/) {
  261. $dim_amount = int(10 * abs($command) / 100); # about 10 levels to 100%
  262. $command = ($command > 0) ? 'L' : 'M';
  263. }
  264. if ($command eq 'M') {
  265. $command = 'L' . (($house . 'L') x $dim_amount);
  266. }
  267. elsif ($command eq 'L') {
  268. $command = 'M' . (($house . 'M') x $dim_amount);
  269. }
  270. elsif ($command eq 'O') {
  271. $command = 'I';
  272. }
  273. elsif ($command eq 'P') {
  274. $command = 'H';
  275. }
  276. $serial_data = 'X' . $device . $house . $command;
  277. # Give weeder a chance to do the previous command
  278. # Surely there must be a better way!
  279. select undef, undef, undef, 1.2;
  280. }
  281. if (lc($port_name) eq 'homevision') {
  282. &Homevision::send($main::Serial_Ports{Homevision}{object}, $serial_data);
  283. }
  284. else {
  285. my $datatype = $main::Serial_Ports{$port_name}{datatype};
  286. $serial_data .= "\r" unless $datatype and $datatype eq 'raw';
  287. my $results = $main::Serial_Ports{$port_name}{object}->write($serial_data);
  288. # &main::print_log("serial port=$port_name out=$serial_data results=$results") if $main::config_parms{debug} eq 'serial';
  289. print "serial port=$port_name out=$serial_data results=$results\n" if $main::config_parms{debug} eq 'serial';
  290. }
  291. }
  292. # Check for X10 All-on All-off house codes
  293. if ($serial_data =~ /^X(\S)([OP])$/) {
  294. print "X10: mh set House code $1 set to $2\n" if $main::config_parms{debug} eq 'X10';
  295. my $state = ($2 eq 'O') ? 'on' : 'off';
  296. &X10_Item::set_by_housecode($1, $state);
  297. }
  298. # Check for other items with the same codes
  299. # - If found, set them to the same state
  300. if ($serial_items_by_id{$serial_id} and my @refs = @{$serial_items_by_id{$serial_id}}) {
  301. for my $ref (@refs) {
  302. next if $ref eq $self;
  303. # Only compare between items on the same port
  304. my $port_name1 = ($self->{port_name} or ' ');
  305. my $port_name2 = ($ref ->{port_name} or ' ');
  306. next unless $port_name1 eq $port_name2;
  307. print "Serial_Item: Setting duplicate state: id=$serial_id item1=$$self{object_name} item2=$$ref{object_name}\n"
  308. if $main::config_parms{debug} eq 'serial';
  309. if ($state = $$ref{state_by_id}{$serial_id}) {
  310. $ref->set_receive($state);
  311. }
  312. else {
  313. $ref->set_receive($serial_id);
  314. }
  315. }
  316. }
  317. }
  318. sub set_interface {
  319. my ($self, $interface) = @_;
  320. # Set the default interface
  321. unless ($interface) {
  322. if ($main::Serial_Ports{cm11}{object}) {
  323. $interface = 'cm11';
  324. }
  325. elsif ($main::Serial_Ports{cm17}{object}) {
  326. $interface = 'cm17';
  327. }
  328. elsif ($main::Serial_Ports{Homevision}{object}) {
  329. $interface = 'homevision';
  330. }
  331. elsif ($main::Serial_Ports{HomeBase}{object}) {
  332. $interface = 'homebase';
  333. }
  334. elsif ($main::Serial_Ports{HouseLinc}{object}) {
  335. $interface = 'houselinc';
  336. }
  337. elsif ($main::Serial_Ports{Marrick}{object}) {
  338. $interface = 'marrick';
  339. }
  340. elsif ($main::config_parms{ncpuxa_port}) {
  341. $interface = 'ncpuxa';
  342. }
  343. }
  344. $$self{interface} = lc($interface) if $interface;
  345. }
  346. #
  347. # $Log$
  348. # Revision 1.41 2000/10/01 23:29:40 winter
  349. # - 2.29 release
  350. #
  351. # Revision 1.40 2000/09/09 21:19:11 winter
  352. # - 2.28 release
  353. #
  354. # Revision 1.39 2000/08/19 01:22:36 winter
  355. # - 2.27 release
  356. #
  357. # Revision 1.38 2000/06/24 22:10:54 winter
  358. # - 2.22 release. Changes to read_table, tk_*, tie_* functions, and hook_ code
  359. #
  360. # Revision 1.37 2000/05/27 16:40:10 winter
  361. # - 2.20 release
  362. #
  363. # Revision 1.36 2000/05/06 16:34:32 winter
  364. # - 2.15 release
  365. #
  366. # Revision 1.35 2000/03/10 04:09:01 winter
  367. # - Add Ibutton support and more web changes
  368. #
  369. # Revision 1.34 2000/02/13 03:57:27 winter
  370. # - 2.00 release. New web server interface
  371. #
  372. # Revision 1.33 2000/02/12 06:11:37 winter
  373. # - commit lots of changes, in preperation for mh release 2.0
  374. #
  375. # Revision 1.32 2000/01/27 13:42:42 winter
  376. # - update version number
  377. #
  378. # Revision 1.31 2000/01/19 13:23:29 winter
  379. # - add yucky delay to Weeder X10 xmit
  380. #
  381. # Revision 1.30 2000/01/02 23:47:43 winter
  382. # - add Device:: to as Serilport check. Use 10, not 7, increments in weeder dim
  383. #
  384. # Revision 1.29 1999/12/09 03:00:21 winter
  385. # - added Weeder bright/dim support
  386. #
  387. # Revision 1.28 1999/11/08 02:16:17 winter
  388. # - Move X10 stuff to X10_Items.pm. Fix close method
  389. #
  390. # Revision 1.27 1999/11/02 14:51:36 winter
  391. # - delete port in any case in stop method
  392. #
  393. # Revision 1.26 1999/10/31 14:49:04 winter
  394. # - added X10 &P## preset dim option and X10_Lamp item
  395. #
  396. # Revision 1.25 1999/10/27 12:42:27 winter
  397. # - add delete to serial_ports_by_port in sub close
  398. #
  399. # Revision 1.24 1999/10/09 20:36:49 winter
  400. # - add call to set_interface in first new method. Change to ControlX10
  401. #
  402. # Revision 1.23 1999/10/02 22:41:10 winter
  403. # - move interface stuff to set_interface, so we can use for x10_appliances also
  404. #
  405. # Revision 1.22 1999/09/27 03:16:32 winter
  406. # - move cm11 to HomeAutomation dir
  407. #
  408. # Revision 1.21 1999/09/12 16:57:07 winter
  409. # - point to new cm17 path
  410. #
  411. # Revision 1.20 1999/08/30 00:23:30 winter
  412. # - add set_dtr set_rts. Add check on loop_count
  413. #
  414. # Revision 1.19 1999/08/02 02:24:21 winter
  415. # - Add STATUS state
  416. #
  417. # Revision 1.18 1999/06/27 20:12:09 winter
  418. # - add CM17 support
  419. #
  420. # Revision 1.17 1999/06/20 22:32:43 winter
  421. # - check for raw datatype on writes
  422. #
  423. # Revision 1.16 1999/04/29 12:25:20 winter
  424. # - add House all on/off states
  425. #
  426. # Revision 1.15 1999/03/21 17:35:36 winter
  427. # - add datatype raw
  428. #
  429. # Revision 1.14 1999/03/12 04:30:24 winter
  430. # - add start, stop, and set_receive methods
  431. #
  432. # Revision 1.13 1999/02/16 02:06:57 winter
  433. # - add homebase send errata
  434. #
  435. # Revision 1.12 1999/02/08 03:50:25 winter
  436. # - re-enable serial writes! Bug introduced in last install.
  437. #
  438. # Revision 1.11 1999/02/08 00:30:54 winter
  439. # - make serial port prints depend on debug parm
  440. #
  441. # Revision 1.10 1999/01/30 19:55:45 winter
  442. # - add more checks for blank objects, so we don't abend
  443. #
  444. # Revision 1.9 1999/01/23 16:23:43 winter
  445. # - change the Serial_Port object to match Socket_Port format
  446. #
  447. # Revision 1.8 1999/01/13 14:11:03 winter
  448. # - add some more debug records
  449. #
  450. # Revision 1.7 1999/01/07 01:55:40 winter
  451. # - add 5% increments on X10_Item
  452. #
  453. # Revision 1.6 1998/12/10 14:34:19 winter
  454. # - fix empty state case
  455. #
  456. # Revision 1.5 1998/12/07 14:33:27 winter
  457. # - add dim level support. Allow for arbitrary set commands.
  458. #
  459. # Revision 1.4 1998/11/15 22:04:26 winter
  460. # - add support for generic serial ports
  461. #
  462. # Revision 1.3 1998/09/12 22:13:14 winter
  463. # - added HomeBase call
  464. #
  465. # Revision 1.2 1998/08/29 20:46:36 winter
  466. # - allow for cm11 interface
  467. #
  468. #
  469. 1;