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

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

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