From c4a712f3106d076f09ccb6783501d97e1f8490f8 Mon Sep 17 00:00:00 2001 From: Winfried Ritsch Date: Tue, 12 Apr 2005 09:14:51 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r2726, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/iem/comport/; revision=2727 --- comport/CHANGES.txt | 8 + comport/LICENCE.txt | 360 ++++++++++++++++++ comport/README.txt | 29 ++ comport/comport.c | 979 +++++++++++++++++++++++++++++++++++++++++++++++++ comport/makefile | 82 +++++ comport/testcomport.pd | 81 ++++ 6 files changed, 1539 insertions(+) create mode 100644 comport/CHANGES.txt create mode 100644 comport/LICENCE.txt create mode 100644 comport/README.txt create mode 100644 comport/comport.c create mode 100644 comport/makefile create mode 100644 comport/testcomport.pd (limited to 'comport') diff --git a/comport/CHANGES.txt b/comport/CHANGES.txt new file mode 100644 index 0000000..3247476 --- /dev/null +++ b/comport/CHANGES.txt @@ -0,0 +1,8 @@ +1.0RC1 - (12.4.2005) + + first check in pure-data.sourceforge.net + added print feature and USB devices from posted by Marc Boon + +0.9beta2 (somedate before 2004) + + CHANGES.txt startet diff --git a/comport/LICENCE.txt b/comport/LICENCE.txt new file mode 100644 index 0000000..9ca2743 --- /dev/null +++ b/comport/LICENCE.txt @@ -0,0 +1,360 @@ +comport - PD External for the serial Ports + +Institute for Electronic Music and Acoustics +Copyright (C) 1998-2005 Winfried Ritsch + + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. (see below) + +--------------------------- GPL.TXT ------------------------- + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + diff --git a/comport/README.txt b/comport/README.txt new file mode 100644 index 0000000..aff44fa --- /dev/null +++ b/comport/README.txt @@ -0,0 +1,29 @@ +comport - PD external for unix/windows to use the serial ports + + (c) 1998-2005 Winfried Ritsch (see LICENCE.txt) + Institute for Electronic Music - Graz + +on Windows the COM0, COM1, ... are used and +under Unix devices /dev/ttyS0, /dev/ttyS1, ... +and new on unix /dev/USB0, ... and can be accessed via a Index. + +Please see testcomport.pd for more help. + +USE: There should be a external comport.dll for windows, comport.pd_linux for linux and so on. + +just copy it to the extra folder of your pd Installation or working directory. +Please see testcomport.pd for more help. + +compile: + + Unix (Linux): + make pd_linux, make pd_irix5, make pd_irix6 + should produce a comport.pd_linux, .... + + + Windows: use nmake or just use Fast Build under MSVC++ + nmake pd_nt + + +If you have improvements or questions feel free to contact me under +ritsch _at_ iem.at diff --git a/comport/comport.c b/comport/comport.c new file mode 100644 index 0000000..0fa9369 --- /dev/null +++ b/comport/comport.c @@ -0,0 +1,979 @@ +/* comport - PD external for unix/windows + + (c) 1998-2005 Winfried Ritsch (see LICENCE.txt) + Institute for Electronic Music - Graz + +*/ + +#include "m_pd.h" + +#ifdef NT +#pragma warning( disable : 4244 ) +#pragma warning( disable : 4305 ) +#include +#include +#else +#include +#include +#include /* for TERMIO ioctl calls */ +#include +#define HANDLE int +#define INVALID_HANDLE_VALUE -1 +#endif + +#include +#include + + +typedef struct comport +{ + t_object x_obj; + + long n; /* the state of a last input */ + + HANDLE comhandle; /* holds the comport handle */ + +#ifdef NT + DCB dcb; /* holds the comm pars */ + DCB dcb_old; /* holds the comm pars */ + COMMTIMEOUTS old_timeouts; +#else + struct termios oldcom_termio; /* save the old com config */ + struct termios com_termio; /* for the new com config */ +#endif + + short comport; /* holds the comport # */ + float baud; /* holds the current baud rate */ + + short rxerrors; /* holds the rx line errors */ + + t_clock *x_clock; + int x_hit; + double x_deltime; + +} t_comport; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifndef ON +#define ON 1 +#define OFF 0 +#endif + +/* + Serial Port Return Values +*/ +#define NODATAAVAIL -1 +#define RXERRORS -2 +#define RXBUFOVERRUN -4 +#define TXBUFOVERRUN -5 + +#ifdef NT + +#define COMPORT_MAX 8 +static char *sys_com_port[COMPORT_MAX] = +{ + "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8" +}; + +static +long baudspeedbittable[] = +{ + CBR_256000, + CBR_128000, + CBR_115200, + CBR_57600, + CBR_56000, + CBR_38400, + CBR_19200, + CBR_14400, + CBR_9600, + CBR_4800, + CBR_2400, + CBR_1200, + CBR_600, + CBR_300, + CBR_110 +}; + +#else /* NT */ + +#ifdef IRIX +#define COMPORT_MAX 2 +static char *sys_com_port[COMPORT_MAX] = +{ + "/dev/ttyd1", "/dev/ttyd2" +}; +#define OPENPARAMS (O_RDWR|O_NDELAY|O_NOCTTY) +#define TIONREAD FIONREAD /* re map the IOCTL function */ +#define BAUDRATE_256000 -1 +#define BAUDRATE_128000 -1 +#define BAUDRATE_115200 -1 +#define BAUDRATE_57600 -1 +#define BAUDRATE_56000 -1 +#define BAUDRATE_38400 B38400 +#define BAUDRATE_14400 B19200 /* 14400 gibts nicht */ +#else /* IRIX */ +#define COMPORT_MAX 16 +static char *sys_com_port[COMPORT_MAX] = +{ + "/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3", + "/dev/ttyS4", "/dev/ttyS5", "/dev/ttyS6", "/dev/ttyS7", + "/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2", "/dev/ttyUSB3", + "/dev/ttyUSB4", "/dev/ttyUSB5", "/dev/ttyUSB6", "/dev/ttyUSB7" +}; +#define OPENPARAMS (O_RDWR|O_NDELAY|O_NOCTTY) +#define BAUDRATE_256000 -1 +#define BAUDRATE_128000 -1 +#define BAUDRATE_115200 B115200 +#define BAUDRATE_57600 B57600 +#define BAUDRATE_56000 B57600 /* 56000 gibts nicht */ +#define BAUDRATE_38400 B38400 +#define BAUDRATE_14400 B19200 /* 14400 gibts nicht */ + +#endif /* else IRIX */ + +static +short baudspeedbittable[] = +{ + BAUDRATE_256000, /* CPU SPECIFIC */ + BAUDRATE_128000, /* CPU SPECIFIC */ + BAUDRATE_115200, /* CPU SPECIFIC */ + BAUDRATE_57600, /* CPU SPECIFIC */ + BAUDRATE_56000, + BAUDRATE_38400, /* CPU SPECIFIC */ + B19200, + BAUDRATE_14400, + B9600, + B4800, + B2400, + B1200, + B600, + B300, + B110 +}; + +struct timeval null_tv; + +#endif /* else NT */ + + +#define BAUDRATETABLE_LEN 15 + +static +long baudratetable[] = +{ + 256000L, + 128000L, + 115200L, + 57600L, + 56000L, + 38400L, + 19200L, + 14400L, + 9600L, + 4800L, + 2400L, + 1200L, + 600L, + 300L, + 110L +}; /* holds the baud rate selections */ + +t_class *comport_class; + +/* --------- sys independend serial setup helpers ---------------- */ + +static long get_baud_ratebits(t_float *baud) +{ + int i = 0; + + while(i < BAUDRATETABLE_LEN && baudratetable[i] > *baud) + i++; + + /* nearest Baudrate finding */ + if(i==BAUDRATETABLE_LEN || baudspeedbittable[i] < 0){ + post("*Warning* The baud rate %d is not suported or out of range, using 9600\n",*baud); + i = 7; + } + *baud = baudratetable[i]; + + return baudspeedbittable[i]; +} + + +/* ------------ sys dependend serial setup helpers ---------------- */ + + +/* --------------------- NT ------------------------------------ */ + +#ifdef NT + + +static float set_baudrate(t_comport *x,t_float baud) +{ + x->dcb.BaudRate = get_baud_ratebits(&baud); + + return baud; +} + +/* bits are 5,6,7,8(default) */ + +static float set_bits(t_comport *x, int nr) +{ + + if(nr < 4 && nr > 8) + nr = 8; + + // number of bits/byte, 4-8 + return x->dcb.ByteSize = nr; +} + + +/* 1 ... Parity even, -1 parity odd , 0 (default) no parity */ +static float set_parity(t_comport *x,int n) +{ + switch(n){ + case 1: + x->dcb.fParity = TRUE; // enable parity checking + x->dcb.Parity = 2; // 0-4=no,odd,even,mark,space + return 1; + + case -1: + x->dcb.fParity = TRUE; // enable parity checking + x->dcb.Parity = 1; // 0-4=no,odd,even,mark,space + return -1; + + default: + x->dcb.fParity = FALSE; // enable parity checking + x->dcb.Parity = 0; // 0-4=no,odd,even,mark,space + } + return 0; +} + + +/* aktivate second stop bit with 1, 0(default)*/ +static float set_stopflag(t_comport *x, int nr) +{ + if(nr == 1){ + x->dcb.StopBits = 1; // 0,1,2 = 1, 1.5, 2 + return 1; + } + else + x->dcb.StopBits = 0; // 0,1,2 = 1, 1.5, 2 + + return 0; +} + +/* never testet */ +static int set_ctsrts(t_comport *x, int nr) +{ + if(nr == 1){ + x->dcb.fOutxCtsFlow = TRUE; // CTS output flow control + x->dcb.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control + return 1; + } + x->dcb.fOutxCtsFlow = FALSE; // CTS output flow control + x->dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS flow control + return 0; +} + +static int set_xonxoff(t_comport *x, int nr) +{ + // x->dcb.fTXContinueOnXoff = FALSE; // XOFF continues Tx + + if(nr == 1){ + x->dcb.fOutX = TRUE; // XON/XOFF out flow control + x->dcb.fInX = TRUE; // XON/XOFF in flow control + return 1; + } + + x->dcb.fOutX = FALSE; // XON/XOFF out flow control + x->dcb.fInX = FALSE; // XON/XOFF in flow control + return 0; +} + + +static int set_serial(t_comport *x) +{ + + if (SetCommState(x->comhandle, &(x->dcb))) + return 1; + + return 0; +} + +static HANDLE open_serial(int com_nr, t_comport *x) +{ + HANDLE fd; + + COMMTIMEOUTS timeouts; + + float *baud = &(x->baud); + + if(com_nr < 0 || com_nr >= COMPORT_MAX) { + post("comport open %d, baud %d not valid (args: [portnum] [baud])",com_nr,*baud); + return INVALID_HANDLE_VALUE; + } + + fd = CreateFile( sys_com_port[com_nr], + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0); + + if(fd == INVALID_HANDLE_VALUE) + { + post("** ERROR ** could not open device %s:\n failure(%d): %s\n", + sys_com_port[com_nr],errno,strerror(errno)); + return INVALID_HANDLE_VALUE; + } + + /* Save the Current Port Configuration */ + + if (!GetCommState(fd, &(x->dcb_old))){ + post("** ERROR ** could not get old dcb of device %s\n", + sys_com_port[com_nr]); + + CloseHandle(fd); + return INVALID_HANDLE_VALUE; + } + + memset(&(x->dcb), sizeof(DCB), 0); + + if (!GetCommState(fd, &(x->dcb))){ + post("** ERROR ** could not get new dcb of device %s\n", + sys_com_port[com_nr]); + + CloseHandle(fd); + return INVALID_HANDLE_VALUE; + } + + + x->dcb.fBinary = TRUE; // binary mode, no EOF check + + // x->dcb.fOutxDsrFlow = FALSE; // DSR output flow control + // x->dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR flow control type + + // x->dcb.fDsrSensitivity = FALSE; // DSR sensitivity + + x->dcb.fErrorChar = FALSE; // enable error replacement + // x->dcb.fNull = FALSE; // enable null stripping + + // DWORD x->dcb.fAbortOnError:1; // abort reads/writes on error + + // char x->dcb.XonChar; // Tx and Rx XON character + // char x->dcb.XoffChar; // Tx and Rx XOFF character + // char x->dcb.ErrorChar; // error replacement character + // char x->dcb.EofChar; // end of input character + // char x->dcb.EvtChar; // received event character + + + set_bits(x,8); /* CS8 */ + set_stopflag(x,0); /* ~CSTOPB */ + set_ctsrts(x,0); /* ~CRTSCTS;*/ + set_xonxoff(x,1); /* (IXON | IXOFF | IXANY) */ + set_baudrate(x,*baud); + + x->comhandle = fd; + + if(set_serial(x)) + { + post("Opened serial line device %s\n",sys_com_port[com_nr]); + } + else + { + post("** ERROR ** could not set params to control dcb of device %s\n", + sys_com_port[com_nr]); + CloseHandle(fd); + return INVALID_HANDLE_VALUE; + } + + + + if (!GetCommTimeouts(fd, &(x->old_timeouts))){ + post("Couldnt get old timeouts for serial device"); + }; + + //setting new timeouts for read to immidiatly return + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + + if (!SetCommTimeouts(fd, &timeouts)){ + post("Couldnt set timeouts for serial device"); + return INVALID_HANDLE_VALUE; + }; + + + return fd; +} + +static HANDLE close_serial(t_comport *x) +{ + if(x->comhandle != INVALID_HANDLE_VALUE){ + + if (!SetCommState(x->comhandle, &(x->dcb_old)) ) + { + post("** ERROR ** could not reset params to DCB of device %s\n", + sys_com_port[x->comport]); + } + + if (!SetCommTimeouts(x->comhandle, &(x->old_timeouts))){ + post("Couldnt reset old_timeouts for serial device"); + }; + + CloseHandle(x->comhandle); + } + + return INVALID_HANDLE_VALUE; +} + + +static int write_serial(t_comport *x, unsigned char chr) +{ + OVERLAPPED osWrite = {0}; + DWORD dwWritten; + DWORD dwRes; + + /* post("open send %d",chr); */ + + if (!WriteFile(x->comhandle, &chr, 1, &dwWritten, &osWrite)) { + if (GetLastError() != ERROR_IO_PENDING) + post("WriteFile failed, but isn't delayed on serialdevice"); + return 0; + } + return 1; +} + +#else /* NT */ +/* ----------------- POSIX - UNIX ------------------------------ */ + + +static float set_baudrate(t_comport *x,t_float baud) +{ + struct termios *tio = &(x->com_termio); + + long baudbits = get_baud_ratebits(&baud); + + cfsetispeed(tio, baudbits); + cfsetospeed(tio, baudbits); + + return baud; +} + +/* bits are 5,6,7,8(default) */ + +static float set_bits(t_comport *x, int nr) +{ + struct termios *tio = &(x->com_termio); + tio->c_cflag &= ~CSIZE; + switch(nr){ + case 5: tio->c_cflag |= CS5; return 5; + case 6: tio->c_cflag |= CS6; return 6; + case 7: tio->c_cflag |= CS7; return 7; + default: tio->c_cflag |= CS8; + } + return 8; +} + + +/* 1 ... Parity even, -1 parity odd , 0 (default) no parity */ +static float set_parity(t_comport *x,int n) +{ + struct termios *tio = &(x->com_termio); + + switch(n){ + case 1: + tio->c_cflag |= PARENB; tio->c_cflag &= ~PARODD; return 1; + case -1: + tio->c_cflag |= PARENB | PARODD; return -1; + default: + tio->c_cflag &= ~PARENB; + } + return 0; +} + + +/* aktivate second stop bit with 1, 0(default)*/ +static float set_stopflag(t_comport *x, int nr) +{ + struct termios *tio = &(x->com_termio); + + if(nr == 1){ + tio->c_cflag |= CSTOPB; + return 1; + } + else + tio->c_cflag &= ~CSTOPB; + return 0; +} + +/* never testet */ +static int set_ctsrts(t_comport *x, int nr) +{ + struct termios *tio = &(x->com_termio); + + if(nr == 1){ + tio->c_cflag |= CRTSCTS; + return 1; + } + tio->c_cflag &= ~CRTSCTS; + return 0; +} + +static int set_xonxoff(t_comport *x, int nr) +{ + struct termios *tio = &(x->com_termio); + + if(nr == 1){ + tio->c_iflag |= (IXON | IXOFF | IXANY); + return 1; + } + + tio->c_iflag &= ~IXON & ~IXOFF & ~IXANY; + return 0; +} + +static int open_serial(int com_nr, t_comport *x) +{ + HANDLE fd; + struct termios *old = &(x->oldcom_termio); + struct termios *new = &(x->com_termio); + float *baud = &(x->baud); + + if(com_nr < 0 || com_nr >= COMPORT_MAX) { + post("comport open %d, baud %d not valid (args: [portnum] [baud])"); + return INVALID_HANDLE_VALUE; + } + + if((fd = open(sys_com_port[com_nr], OPENPARAMS)) == INVALID_HANDLE_VALUE) + { + post("** ERROR ** could not open device %s:\n failure(%d): %s\n", + sys_com_port[com_nr],errno,strerror(errno)); + return INVALID_HANDLE_VALUE; + } + + /* set no wait on any operation */ + fcntl(fd, F_SETFL, FNDELAY); + + /* Save the Current Port Configuration */ + if(tcgetattr(fd, old) == -1 || tcgetattr(fd, new) == -1){ + post("** ERROR ** could not get termios-structure of device %s\n", + sys_com_port[com_nr]); + close(fd); + return INVALID_HANDLE_VALUE; + } + + + /* Setupt the new port configuration...NON-CANONICAL INPUT MODE + .. as defined in termios.h + */ + + /* enable input and ignore modem controls */ + new->c_cflag |= (CREAD | CLOCAL); + + /* always nocanonical, this means raw i/o no terminal */ + new->c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + /* no post processing */ + new->c_oflag &= ~OPOST; + + /* setup to return after 0 seconds + ..if no characters are received + TIME units are assumed to be 0.1 secs */ + /* not needed anymore ??? in new termios in linux + new->c_cc[VMIN] = 0; + new->c_cc[VTIME] = 0; + */ + + /* defaults, see input */ + + set_bits(x,8); /* CS8 */ + set_stopflag(x,0); /* ~CSTOPB */ + set_ctsrts(x,0); /* ~CRTSCTS;*/ + set_xonxoff(x,1); /* (IXON | IXOFF | IXANY) */ + set_baudrate(x,*baud); + + if(tcsetattr(fd, TCSAFLUSH, new) != -1) + { + post("Opened serial line device %s\n",sys_com_port[com_nr]); + } + else + { + post("** ERROR ** could not set params to ioctl of device %s\n", + sys_com_port[com_nr]); + close(fd); + return INVALID_HANDLE_VALUE; + } + + return fd; +} + + +static int close_serial(t_comport *x) +{ + struct termios *tios = &(x->com_termio); + HANDLE fd = x->comhandle; + + if(fd != INVALID_HANDLE_VALUE){ + tcsetattr(fd, TCSANOW, tios); + close(fd); + } + return INVALID_HANDLE_VALUE; +} + + +static int set_serial(t_comport *x) +{ + if(tcsetattr(x->comhandle, TCSAFLUSH, &(x->com_termio)) == -1) + return 0; + return 1; +} + +static int write_serial(t_comport *x, unsigned char chr) +{ + + return write(x->comhandle,(char *) &chr,1); + + /* flush pending I/O chars */ + /* but nowadays discards them ;-( + else{ + ioctl(x->comhandle,TCFLSH,TCOFLUSH); + } + */ +} + + +#endif /* else NT */ + + +/* ------------------- serial pd methods --------------------------- */ +static void comport_pollintervall(t_comport *x, t_floatarg g) +{ + if (g < 1) g = 1; + x->x_deltime = g; +} + +static void comport_tick(t_comport *x) +{ + unsigned char chr; + int err; + HANDLE fd = x->comhandle; + + x->x_hit = 0; + + + if(fd == INVALID_HANDLE_VALUE) return; + + /* while there are bytes, read them and send them out, ignore errors */ +#ifdef NT + { + DWORD dwCommEvent; + DWORD dwRead; + + err = 0; + +// if (!SetCommMask(x->comhandle, EV_RXCHAR)) +// post(" Error setting communications event mask for serial device"); + +// for ( ; ; ) { +// if (WaitCommEvent(x->comhandle, &dwCommEvent,NULL)) { + do { + + if(ReadFile(x->comhandle, &chr, 1, &dwRead, NULL)) + if(dwRead > 0) + outlet_float(x->x_obj.ob_outlet, (t_float) chr); + else{ + err = -1; + break; + } + } while (dwRead); +// } +// else{ +// post("serial dev: Error in WaitCommEvent"); + // break; +// } + //} + } +#else + { + fd_set com_rfds; + FD_ZERO(&com_rfds); + FD_SET(fd,&com_rfds); + + while((err=select(fd+1,&com_rfds,NULL,NULL,&null_tv)) > 0) { + + err = read(fd,(char *) &chr,1); + + /* while( (err = read(fd,(char *) &chr,1)) > 0){ */ + outlet_float(x->x_obj.ob_outlet, (t_float) chr); + + }; + } +#endif + + if(err < 0){ /* if an readerror detected */ + if(x->rxerrors == 0) /* post it once */ + post("RXERRORS on serial line\n"); + x->rxerrors = 1; /* remember */ + } + + if (!x->x_hit) clock_delay(x->x_clock, 1); +} + +static void comport_float(t_comport *x,t_float f) +{ + unsigned char chr = ((int) f) & 0xFF; /* brutal conv */ + + if (write_serial(x,chr) != 1) + { + post("Write error, maybe TX-OVERRUNS on serial line"); + } +} + +static void *comport_new(t_floatarg comnr, t_floatarg fbaud) { + + t_comport test; + t_comport *x; + int com_nr = comnr; + HANDLE fd; + + +/* Open the Comport for RD and WR and get a handle */ + test.baud = fbaud; + fd = open_serial(com_nr,&test); + + if(fd == INVALID_HANDLE_VALUE ){ + /* postings in open routine */ + return 0; + } + + /* Now nothing really bad could happen so we create the class */ + + x = (t_comport *)pd_new(comport_class); + + x->comport = com_nr; + x->baud = test.baud; + x->comhandle = fd; /* holds the comport handle */ + +#ifdef NT + + memcpy(&(test.dcb_old),&(x->dcb_old),sizeof(DCB)); // save the old com config + memcpy(&(test.dcb),&(x->dcb),sizeof(DCB)); // for the new com config + +#else + /* save the old com and new com config */ + bcopy(&(test.oldcom_termio),&(x->oldcom_termio),sizeof(struct termios)); + bcopy(&(test.com_termio),&(x->com_termio),sizeof(struct termios)); +#endif + + x->rxerrors = 0; /* holds the rx line errors */ + + outlet_new(&x->x_obj, &s_float); + + x->x_hit = 0; + x->x_deltime = 1; + x->x_clock = clock_new(x, (t_method)comport_tick); + + clock_delay(x->x_clock, x->x_deltime); + + return x; +} + + +static void +comport_free(t_comport *x) +{ + post("close serial..."); + + clock_unset(x->x_clock); + clock_free(x->x_clock); + x->comhandle = close_serial(x); +} + +/* ---------------- use serial settings ------------- */ + +static void comport_baud(t_comport *x,t_floatarg f) +{ + if(f == x->baud){ + post("baudrate already %f\n",x->baud); + return; + } + + x->baud = set_baudrate(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set baudrate of device %s\n", + sys_com_port[x->comport]); + } + else post("set baudrate of %s to %f\n",sys_com_port[x->comport],x->baud); +} + +static void comport_bits(t_comport *x,t_floatarg f) +{ + f = set_bits(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set bits of device %s\n", + sys_com_port[x->comport]); + } + else post("set bits of %s to %f\n",sys_com_port[x->comport],f); +} + + +static void comport_parity(t_comport *x,t_floatarg f) +{ + f = set_parity(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set extra paritybit of device %s\n", + sys_com_port[x->comport]); + } + else post("set extra paritybit of %s to %f\n",sys_com_port[x->comport],f); +} + +static void comport_stopbit(t_comport *x,t_floatarg f) +{ + f = set_stopflag(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set extra stopbit of device %s\n", + sys_com_port[x->comport]); + } + else post("set extra stopbit of %s to %f\n",sys_com_port[x->comport],f); +} + +static void comport_rtscts(t_comport *x,t_floatarg f) +{ + f = set_ctsrts(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set rts_cts of device %s\n", + sys_com_port[x->comport]); + } + else post("set rts-cts of %s to %f\n",sys_com_port[x->comport],f); +} + +static void comport_xonxoff(t_comport *x,t_floatarg f) +{ + f = set_xonxoff(x,f); + + if(x->comhandle == INVALID_HANDLE_VALUE)return; + + if(set_serial(x) == 0){ + post("** ERROR ** could not set xonxoff of device %s\n", + sys_com_port[x->comport]); + } + else post("set xonxoff of %s to %f\n",sys_com_port[x->comport],f); +} + +static void comport_close(t_comport *x) +{ + clock_unset(x->x_clock); + x->x_hit = 1; + x->comhandle = close_serial(x); +} + +static void comport_open(t_comport *x, t_floatarg f) +{ + if(x->comhandle != INVALID_HANDLE_VALUE) + comport_close(x); + + + x->comhandle = open_serial(f,x); + + clock_delay(x->x_clock, x->x_deltime); +} + +/* + dangerous but if you really have some stupid devicename ... + you can open any file on systems if suid is set on pd be careful +*/ + +static void comport_devicename(t_comport *x, t_symbol *s) +{ + if(x->comport >= 0 && x->comport < COMPORT_MAX){ + sys_com_port[x->comport] = s->s_name; + } +} + +static void comport_print(t_comport *x, t_symbol *s, int argc, t_atom *argv) +{ + static char buf[256]; + while(argc--) { + atom_string(argv++, buf, 255); + char *pch = buf; + while(*pch != 0) { + write_serial(x, *pch++); + } + if(argc > 0) { + write_serial(x, ' '); + } + } +} +/* ---------------- SETUP OBJECTS ------------------ */ + +void comport_setup(void) +{ + comport_class + = class_new(gensym("comport"), (t_newmethod)comport_new, + (t_method)comport_free, sizeof(t_comport), + 0, A_DEFFLOAT, A_DEFFLOAT, 0); + + class_addfloat(comport_class, (t_method)comport_float); + + /* + class_addbang(comport_class, comport_bang + */ + + + class_addmethod(comport_class, (t_method)comport_baud, gensym("baud"), + A_FLOAT, 0); + + + class_addmethod(comport_class, (t_method)comport_bits, gensym("bits"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_stopbit, gensym("stopbit"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_rtscts, gensym("rtscts"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_parity, gensym("parity"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_xonxoff, gensym("xonxoff"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_close, gensym("close"), 0); + class_addmethod(comport_class, (t_method)comport_open, gensym("open"), + A_FLOAT, 0); + class_addmethod(comport_class, (t_method)comport_devicename, gensym("devicename"), + A_SYMBOL, 0); + class_addmethod(comport_class, (t_method)comport_print, gensym("print"), + A_GIMME, 0); + + class_addmethod(comport_class, (t_method)comport_pollintervall, gensym("pollintervall"), + A_FLOAT, 0); +#ifndef NT + null_tv.tv_sec = 0; /* no wait */ + null_tv.tv_usec = 0; +#endif +} + + diff --git a/comport/makefile b/comport/makefile new file mode 100644 index 0000000..48e9db2 --- /dev/null +++ b/comport/makefile @@ -0,0 +1,82 @@ +# comport - PD external for unix/windows +# +# (c) 1998-2005 Winfried Ritsch (see LICENCE.txt) +# Institute for Electronic Music - Graz +# +# + +current: + echo choose one command: make pd_linux, make pd_nt, make pd_irix5, make pd_irix6 + +# ----------------------- NT ----------------------- +pd_nt: comport.dll + +.SUFFIXES: .dll + +PDNTCFLAGS = /W3 /WX /DNT /DPD /nologo /DWIN2000 + +VC="C:\Programme\Microsoft Visual Studio\Vc98" +PDROOT="C:\Programme\pd" + +PDNTINCLUDE = /I. /I$(PDROOT)\tcl\include /I$(PDROOT)\src /I$(VC)\include + +PDNTLDIR = $(VC)\lib +PDNTLIB = $(PDNTLDIR)\libc.lib \ + $(PDNTLDIR)\oldnames.lib \ + $(PDNTLDIR)\kernel32.lib \ + $(PDROOT)\bin\pd.lib + +.c.dll: + cl $(PDNTCFLAGS) $(PDNTINCLUDE) /c $*.c + link /dll /export:$*_setup $*.obj $(PDNTLIB) + +# ----------------------- IRIX 5.x ----------------------- +pd_irix5: comport.pd_irix5 + +.SUFFIXES: .pd_irix5 + +SGICFLAGS5 = -o32 -DPD -DSGI -O2 + + +SGIINCLUDE = -I../../src + +.c.pd_irix5: + cc $(SGICFLAGS5) $(SGIINCLUDE) -o $*.o -c $*.c + ld -elf -shared -rdata_shared -o $*.pd_irix5 $*.o + rm $*.o + +# ----------------------- IRIX 6.x ----------------------- +pd_irix6: comport.pd_irix6 + +.SUFFIXES: .pd_irix6 + +SGICFLAGS6 = -DPD -DSGI -n32 \ + -OPT:roundoff=3 -OPT:IEEE_arithmetic=3 -OPT:cray_ivdep=true \ + -Ofast=ip32 + +SGICFLAGS5 = -DPD -O2 -DSGI + +SGIINCLUDE = -I/../../src + +.c.pd_irix6: + cc $(SGICFLAGS6) $(SGIINCLUDE) -o $*.o -c $*.c + ld -elf -shared -rdata_shared -o $*.pd_irix6 $*.o + rm $*.o + +# ----------------------- LINUX i386 ----------------------- + +pd_linux: comport.pd_linux + +.SUFFIXES: .pd_linux + +LINUXCFLAGS = -DPD -O2 -funroll-loops -fomit-frame-pointer \ + -Wall -W -Wshadow -Wstrict-prototypes -Werror \ + -Wno-unused -Wno-parentheses -Wno-switch + +LINUXINCLUDE = -I../../src + +.c.pd_linux: + cc $(LINUXCFLAGS) $(LINUXINCLUDE) -o $*.o -c $*.c + ld -export_dynamic -shared -o $*.pd_linux $*.o -lc -lm + strip --strip-unneeded $*.pd_linux + rm $*.o diff --git a/comport/testcomport.pd b/comport/testcomport.pd new file mode 100644 index 0000000..0dbfcf2 --- /dev/null +++ b/comport/testcomport.pd @@ -0,0 +1,81 @@ +#N canvas 199 244 693 475 10; +#X obj 97 231 comport 1 9600; +#X msg 13 213 66; +#X obj 88 361 print; +#X floatatom 195 190 4 0 0 0 - - -; +#X msg 12 171 64; +#X text 12 191 point; +#X text 11 150 stream; +#X msg 13 263 86; +#X text 13 238 position; +#X msg 16 307 70; +#X msg 14 347 71; +#X text 15 290 run; +#X text 14 325 sleep; +#X floatatom 121 269 4 0 0 0 - - -; +#X obj 90 325 spigot; +#X msg 109 298 1; +#X msg 143 299 0; +#X msg 314 48 bits 8; +#X msg 313 72 stopbit 0; +#X msg 312 100 parity 0; +#X msg 313 129 xonxoff 1; +#X msg 313 155 rtscts 0; +#X text 392 103 parity 1=even \, -1=odd \, 0=off; +#X text 392 74 extra stopbit 1=on \, 0=off; +#X text 394 49 databits 5 \, 6 \, 7 \, 8; +#X text 393 20 use exact or higher baudrate; +#X obj 94 200 r comctl; +#X obj 271 294 s comctl; +#X text 394 128 use handshake xon/off 1=on 0=off; +#X text 392 154 cts/rts hardwarehandshake 1=on 0=off; +#X msg 318 201 pollintervall 1; +#X text 430 191 set pollintervall for read in ms; +#X text 430 205 (default is 1 tick 1ms); +#X msg 317 233 close; +#X msg 317 259 open 1; +#X msg 314 23 baud 10000; +#X text 383 230 Close Serial port; +#X text 381 263 Open seriel board Nr (0=COM1 \, 1=COM2 \, ...); +#X msg 263 354 devicename /dev/ttyS1; +#X text 257 375 Danger !!! you can open every file in your system and +maybe if suid is root damage the system.; +#X text 258 336 set devicename for actuell port \, then close and open +again; +#X obj 195 165 spigot; +#X msg 214 138 1; +#X msg 247 143 0; +#X obj 190 112 key; +#X text 260 324 never should be needed except for sys admins (only +unix); +#X msg 309 0 baud 300; +#X text 3 -10 (C) 1998-2005 IEM Winfried Ritsch GPL (see LICENSE.txt) +; +#X text 14 388; +#X text 1 41 please improve...; +#X connect 0 0 13 0; +#X connect 0 0 14 0; +#X connect 1 0 0 0; +#X connect 3 0 0 0; +#X connect 4 0 0 0; +#X connect 7 0 0 0; +#X connect 9 0 0 0; +#X connect 10 0 0 0; +#X connect 14 0 2 0; +#X connect 15 0 14 1; +#X connect 16 0 14 1; +#X connect 17 0 27 0; +#X connect 18 0 27 0; +#X connect 19 0 27 0; +#X connect 20 0 27 0; +#X connect 21 0 27 0; +#X connect 26 0 0 0; +#X connect 30 0 27 0; +#X connect 33 0 27 0; +#X connect 34 0 27 0; +#X connect 35 0 27 0; +#X connect 41 0 3 0; +#X connect 42 0 41 1; +#X connect 43 0 41 1; +#X connect 44 0 41 0; +#X connect 46 0 27 0; -- cgit v1.2.1