├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── examples └── lcd.py ├── makefile ├── parallel ├── __init__.py ├── parallelppdev.py ├── parallelutil.py └── parallelwin32.py ├── setup.py ├── src └── win32 │ ├── README.txt │ ├── _pyparallel.c │ ├── giveio │ ├── GIVEIO.C │ ├── MAKEFILE │ ├── README.TXT │ └── SOURCES │ ├── install_giveio.bat │ ├── loaddrv_console │ ├── loaddrv.c │ ├── loaddrv.h │ └── makefile │ ├── makefile │ ├── remove_giveio.bat │ ├── simpleio.c │ └── simpleio.dll └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | build/ 4 | dist/ 5 | *.egg* 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Chris Liechti 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | language: python 5 | 6 | python: 7 | - 2.7 8 | - 3.2 9 | - 3.3 10 | - 3.4 11 | - 3.5 12 | # - pypy 13 | 14 | script: 15 | - python setup.py install 16 | - python setup.py flake8 17 | # - python test/test.py loop:// 18 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | pyParallel Release Notes 3 | ========================== 4 | 5 | Version 0.1 29 Jul 2002 6 | --------------------------- 7 | - added to CVS 8 | 9 | Version 0.2 27 Jan 2005 10 | --------------------------- 11 | - Windows version now using ctypes 12 | 13 | Version ... ... 14 | --------------------------- 15 | - add setDataDir to Windows backend 16 | - [SF 2785532] add getData for ppdev backend 17 | 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2001-2015 Chris Liechti 2 | All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | --------------------------------------------------------------------------- 33 | Note: 34 | Individual files contain the following tag instead of the full license text. 35 | 36 | SPDX-License-Identifier: BSD-3-Clause 37 | 38 | This enables machine processing of license information based on the SPDX 39 | License Identifiers that are here available: http://spdx.org/licenses/ 40 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.rst 2 | include LICENSE.txt 3 | include README.rst 4 | include src/win32/simpleio.dll 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | pyParallel [in development] 3 | ============================ 4 | 5 | .. image:: https://travis-ci.org/pyserial/pyparallel.svg?branch=master 6 | :target: https://travis-ci.org/pyserial/pyparallel 7 | :alt: Build Status 8 | 9 | .. image:: https://img.shields.io/pypi/dw/pyparallel.svg 10 | :target: https://pypi.python.org/pypi/pyparallel/ 11 | :alt: PyPI Downloads 12 | 13 | .. image:: https://img.shields.io/pypi/v/pyparallel.svg 14 | :target: https://pypi.python.org/pypi/pyparallel/ 15 | :alt: Latest PyPI version 16 | 17 | .. image:: https://img.shields.io/pypi/l/pyparallel.svg 18 | :target: https://pypi.python.org/pypi/pyparallel/ 19 | :alt: License 20 | 21 | Overview 22 | ======== 23 | This module encapsulates the access for the parallel port. It provides 24 | backends for Python running on Windows and Linux. Other platforms are 25 | possible too but not yet integrated. 26 | 27 | This module is still under development. But it may be useful for 28 | developers. 29 | The Windows version needs a compiled extension and the giveio.sys driver 30 | for Windows NT/2k/XP. It uses ``ctypes`` to access functions in a prebuilt 31 | DLL. 32 | 33 | It is released under a free software license, see LICENSE.rst for more 34 | details. 35 | 36 | Copyright (C) 2001-2016 Chris Liechti cliechti@gmx.net 37 | 38 | Homepage: https://github.com/pyserial/pyparallel 39 | 40 | 41 | Features 42 | ======== 43 | - same class based interface on all supported platforms 44 | - port numbering starts at zero, no need to know the port name in the 45 | user program 46 | - port string (device name) can be specified if access through numbering 47 | is inappropriate 48 | 49 | 50 | Requirements 51 | ============ 52 | - Python 2.2 or newer 53 | - "Java Communications" (JavaComm) extension for Java/Jython 54 | 55 | 56 | Installation 57 | ============ 58 | Extract files from the archive, open a shell/console in that directory and 59 | let Distutils do the rest: 60 | 61 | .. code-block:: bash 62 | 63 | $ python setup.py install 64 | 65 | 66 | Short introduction 67 | ------------------ 68 | 69 | .. code-block:: python 70 | 71 | >>> import parallel 72 | >>> p = parallel.Parallel() # open LPT1 or /dev/parport0 73 | >>> p.setData(0x55) 74 | 75 | 76 | Examples 77 | ======== 78 | Please look in the GIT Repository. There is an example directory where you 79 | can find a simple terminal and more. 80 | https://github.com/pyserial/pyparallel/tree/master/examples 81 | 82 | 83 | References 84 | ========== 85 | - Python: http://www.python.org 86 | 87 | -------------------------------------------------------------------------------- /examples/lcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # character LCD example for pyparallel 3 | # 4 | #(C) 2002 Chris Liechti 5 | # this is distributed under a free software license, see license.txt 6 | 7 | import sys, time 8 | sys.path.insert(0, '..') 9 | import parallel 10 | 11 | LCDON = 0x01 #0x00000001 Switch on display 12 | LCDOFF = 0x08 #0x00001000 Switch off display 13 | LCDCLEAR = 0x01 #0x00000001 14 | LCDLINE1 = 0x80 #0x10000000 15 | LCDLINE2 = 0xc0 #0x11000000 16 | LCDCURSORON = 0x0f #0x00001111 turn on cursor blinking 17 | LCDCURSOROFF = 0x0c #0x00001100 Disable cursor blinking. The cursor is hidden. 18 | LCDCGADRSET = 0x40 #0b01000000 19 | LCDDDADRSET = 0x80 #0b10000000 20 | LCD2LINES = 0x28 #0b00101000 Set display mode to two lines. 21 | LCD8BITS = 0x30 #0b00110000 select 8 Bit interface 22 | LCD4BITS = 0x20 #0b00100000 select 4 Bit interface 23 | LCD_DATA_OFF = 0x05 #0x00000101 mask used to clear the data lines 24 | 25 | LCD_RS = 1<<1 26 | LCD_RW = 1<<2 27 | LCD_E = 1<<3 28 | LCD_D4 = 1<<4 29 | LCD_D5 = 1<<5 30 | LCD_D6 = 1<<6 31 | LCD_D7 = 1<<7 32 | 33 | 34 | class FourBitIO(object): 35 | def __init__(self): 36 | self.data = 0 37 | 38 | self.out(0) #reset pins 39 | time.sleep(0.050) #wait more than 30ms 40 | #send the reset sequece (3 times the same pattern) 41 | self.out(LCD8BITS) #set 8 bit interface 42 | self.toggleE() #toggle LCD_E, the enable pin 43 | time.sleep(0.005) #wait a bit 44 | self.toggleE() #toggle LCD_E, the enable pin 45 | time.sleep(0.005) #wait a bit 46 | self.toggleE() #toggle LCD_E, the enable pin 47 | time.sleep(0.005) #wait a bit 48 | 49 | self.out(LCD4BITS) #now set up the 4 bit interface 50 | self.toggleE() #toggle LCD_E, the enable pin 51 | time.sleep(0.002) #wait until instr is finished 52 | self.instr(LCD2LINES) #set 2 lines display 53 | self.instr(LCDCURSOROFF) #hide cursor 54 | self.instr(LCDCLEAR) #clear display 55 | 56 | #my connector has the wrong pinorder.... 57 | #better swap them in software than to solder ;-) 58 | def reveseout(self, x): 59 | r = ((x & (1<<0) and 1) << 7) |\ 60 | ((x & (1<<1) and 1) << 6) |\ 61 | ((x & (1<<2) and 1) << 5) |\ 62 | ((x & (1<<3) and 1) << 4) |\ 63 | ((x & (1<<4) and 1) << 3) |\ 64 | ((x & (1<<5) and 1) << 2) |\ 65 | ((x & (1<<6) and 1) << 1) |\ 66 | ((x & (1<<7) and 1) << 0) 67 | #print "%02x" % r, "%02x" %x 68 | self.p.setData(r) 69 | 70 | def toggleE(self): 71 | """toggle enable pin""" 72 | self.data |= LCD_E; #toggle LCD_E, the enable pin 73 | self.reveseout(self.data) 74 | self.data &= ~LCD_E; #back to inactive position 75 | self.reveseout(self.data) 76 | 77 | def out(self, data): 78 | """set data to LCD port""" 79 | self.data = data 80 | self.reveseout(self.data) 81 | 82 | def instr(self, cmd): 83 | """send instruction byte to LCD""" 84 | self.out(cmd & 0xf0) #output upper nibble 85 | self.toggleE() #toggle LCD_E, the enable pin 86 | self.out((cmd << 4) & 0xf0) #and then the lower nibble 87 | self.toggleE() #toggle LCD_E, the enable pin 88 | time.sleep(0.001) #wait until instr is finished 89 | 90 | def putc(self, c): 91 | """send a data byte to the LCD""" 92 | c = ord(c) 93 | self.out((c & 0xf0) | LCD_RS) #output upper nibble 94 | self.toggleE() #toggle LCD_E, the enable pin 95 | self.out(((c << 4) & 0xf0) | LCD_RS) #and then the lower nibble 96 | self.toggleE() #toggle LCD_E, the enable pin 97 | time.sleep(0.001) #wait until instr is finished 98 | 99 | 100 | class EightBitIO(object): 101 | def __init__(self): 102 | self.data = 0 103 | 104 | self.setRS(0) 105 | self.setRW(0) 106 | self.out(0) #reset pins 107 | time.sleep(0.050) #wait more than 30ms 108 | #send the reset sequece (3 times the same pattern) 109 | self.out(LCD8BITS) #set 8 bit interface 110 | self.toggleE() #toggle LCD_E, the enable pin 111 | time.sleep(0.005) #wait a bit 112 | self.toggleE() #toggle LCD_E, the enable pin 113 | time.sleep(0.005) #wait a bit 114 | self.toggleE() #toggle LCD_E, the enable pin 115 | time.sleep(0.005) #wait a bit 116 | 117 | #~ self.instr(LCD2LINES) #set 2 lines display 118 | self.instr(LCDCURSOROFF) #hide cursor 119 | self.instr(LCDCLEAR) #clear display 120 | 121 | def setRW(self, state): 122 | self.p.setAutoFeed(state) 123 | 124 | def setRS(self, state): 125 | self.p.setInitOut(state) 126 | 127 | def toggleE(self): 128 | """toggle enable pin""" 129 | self.p.setDataStrobe(1) #toggle LCD_E, the enable pin 130 | #~ time.sleep(0.001) 131 | self.p.setDataStrobe(0) #back to inactive position 132 | #~ time.sleep(0.001) 133 | 134 | def out(self, data): 135 | """set data to LCD port""" 136 | self.data = data 137 | self.p.setData(self.data) 138 | 139 | def instr(self, cmd): 140 | """send instruction byte to LCD""" 141 | self.setRS(0) 142 | self.setRW(0) 143 | self.out(cmd) 144 | self.toggleE() #toggle LCD_E, the enable pin 145 | time.sleep(0.005) #wait until instr is finished 146 | 147 | def putc(self, c): 148 | """send a data byte to the LCD""" 149 | self.setRS(1) 150 | self.setRW(0) 151 | self.out(ord(c)) 152 | self.toggleE() #toggle LCD_E, the enable pin 153 | time.sleep(0.001) #wait until instr is finished 154 | 155 | 156 | #~ class HD44780(FourBitIO): 157 | class HD44780(EightBitIO): 158 | def __init__(self): 159 | self.p = parallel.Parallel() 160 | super(HD44780, self).__init__() 161 | 162 | def write(self, str): 163 | """write a string to the LCD""" 164 | for c in str: 165 | self.putc(c) #write each character 166 | 167 | def downloadFont(self, fontdata): 168 | """Set the memory pointer and download a font""" 169 | self.instr(LCDCGADRSET); 170 | self.write(fontdata) 171 | self.instr(LCDLINE1) #just in case, set cursor to a visible pos 172 | 173 | if __name__ == '__main__': 174 | lcd = HD44780() 175 | lcd.write("Hello World") 176 | lcd.instr(LCDLINE2) 177 | lcd.write("from Python") 178 | ## for c in map(chr,range(256)): 179 | ## lcd.instr(LCDLINE1) 180 | ## lcd.write(c) 181 | 182 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./setup.py build --compiler=mingw32 3 | cp build/lib.win32-2.2/_pyparallel.pyd ./parallel 4 | 5 | installer: all 6 | ./setup.py bdist_wininst 7 | -------------------------------------------------------------------------------- /parallel/__init__.py: -------------------------------------------------------------------------------- 1 | # portable parallel port access with python 2 | # this is a wrapper module for different platform implementations 3 | # 4 | # (C)2001-2002 Chris Liechti 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | 8 | import os 9 | VERSION = "0.3" 10 | 11 | # choose an implementation, depending on os 12 | if os.name == 'nt': 13 | from parallel.parallelwin32 import Parallel # noqa 14 | elif os.name == 'posix': 15 | from parallel.parallelppdev import Parallel # noqa 16 | else: 17 | raise "Sorry no implementation for your platform available." 18 | -------------------------------------------------------------------------------- /parallel/parallelppdev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # parallel port access using the ppdev driver 3 | 4 | import sys 5 | import struct 6 | import fcntl 7 | import os 8 | 9 | # Generated by h2py 0.1.1 from , 10 | # then cleaned up a bit by Michael P. Ashton and then a gain by chris ;-) 11 | # Changes for Python2.2 support (c) September 2004 Alex.Perry@qm.com 12 | 13 | 14 | def sizeof(type): 15 | return struct.calcsize(type) 16 | 17 | 18 | def _IOC(dir, type, nr, size): 19 | return int((dir << _IOC_DIRSHIFT) | (type << _IOC_TYPESHIFT) | 20 | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)) 21 | 22 | 23 | def _IO(type, nr): 24 | return _IOC(_IOC_NONE, type, nr, 0) 25 | 26 | 27 | def _IOR(type, nr, size): 28 | return _IOC(_IOC_READ, type, nr, sizeof(size)) 29 | 30 | 31 | def _IOW(type, nr, size): 32 | return _IOC(_IOC_WRITE, type, nr, sizeof(size)) 33 | 34 | _IOC_SIZEBITS = 14 35 | _IOC_SIZEMASK = (1 << _IOC_SIZEBITS) - 1 36 | _IOC_NRSHIFT = 0 37 | _IOC_NRBITS = 8 38 | _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS 39 | _IOC_TYPEBITS = 8 40 | _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS 41 | IOCSIZE_MASK = _IOC_SIZEMASK << _IOC_SIZESHIFT 42 | IOCSIZE_SHIFT = _IOC_SIZESHIFT 43 | 44 | # Python 2.2 uses a signed int for the ioctl() call, so ... 45 | if sys.version_info[0] < 3 or sys.version_info[1] < 3: 46 | _IOC_WRITE = 1 47 | _IOC_READ = -2 48 | _IOC_INOUT = -1 49 | else: 50 | _IOC_WRITE = 1 51 | _IOC_READ = 2 52 | _IOC_INOUT = 3 53 | 54 | _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS 55 | IOC_INOUT = _IOC_INOUT << _IOC_DIRSHIFT 56 | IOC_IN = _IOC_WRITE << _IOC_DIRSHIFT 57 | IOC_OUT = _IOC_READ << _IOC_DIRSHIFT 58 | 59 | _IOC_NONE = 0 60 | PP_IOCTL = ord('p') 61 | PPCLAIM = _IO(PP_IOCTL, 0x8b) 62 | PPCLRIRQ = _IOR(PP_IOCTL, 0x93, 'i') 63 | 64 | PPDATADIR = _IOW(PP_IOCTL, 0x90, 'i') 65 | PPEXCL = _IO(PP_IOCTL, 0x8f) 66 | PPFCONTROL = _IOW(PP_IOCTL, 0x8e, 'BB') 67 | PPGETFLAGS = _IOR(PP_IOCTL, 0x9a, 'i') 68 | PPGETMODE = _IOR(PP_IOCTL, 0x98, 'i') 69 | PPGETMODES = _IOR(PP_IOCTL, 0x97, 'I') 70 | PPGETPHASE = _IOR(PP_IOCTL, 0x99, 'i') 71 | PPGETTIME = _IOR(PP_IOCTL, 0x95, 'll') 72 | PPNEGOT = _IOW(PP_IOCTL, 0x91, 'i') 73 | PPRCONTROL = _IOR(PP_IOCTL, 0x83, 'B') 74 | PPRDATA = _IOR(PP_IOCTL, 0x85, 'B') 75 | # 'OBSOLETE__IOR' undefined in 'PPRECONTROL' 76 | PPRELEASE = _IO(PP_IOCTL, 0x8c) 77 | # 'OBSOLETE__IOR' undefined in 'PPRFIFO' 78 | PPRSTATUS = _IOR(PP_IOCTL, 0x81, 'B') 79 | PPSETFLAGS = _IOW(PP_IOCTL, 0x9b, 'i') 80 | PPSETMODE = _IOW(PP_IOCTL, 0x80, 'i') 81 | PPSETPHASE = _IOW(PP_IOCTL, 0x94, 'i') 82 | PPSETTIME = _IOW(PP_IOCTL, 0x96, 'll') 83 | PPWCONTROL = _IOW(PP_IOCTL, 0x84, 'B') 84 | PPWCTLONIRQ = _IOW(PP_IOCTL, 0x92, 'B') 85 | PPWDATA = _IOW(PP_IOCTL, 0x86, 'B') 86 | # 'OBSOLETE__IOW' undefined in 'PPWECONTROL' 87 | # 'OBSOLETE__IOW' undefined in 'PPWFIFO' 88 | # 'OBSOLETE__IOW' undefined in 'PPWSTATUS' 89 | PPYIELD = _IO(PP_IOCTL, 0x8d) 90 | PP_FASTREAD = 1 << 3 91 | PP_FASTWRITE = 1 << 2 92 | PP_W91284PIC = 1 << 4 93 | PP_FLAGMASK = PP_FASTWRITE | PP_FASTREAD | PP_W91284PIC 94 | PP_MAJOR = 99 95 | _ASMI386_IOCTL_H = None 96 | _IOC_DIRBITS = 2 97 | _IOC_DIRMASK = (1 << _IOC_DIRBITS) - 1 98 | _IOC_NRMASK = (1 << _IOC_NRBITS) - 1 99 | _IOC_TYPEMASK = (1 << _IOC_TYPEBITS) - 1 100 | 101 | 102 | def _IOC_DIR(nr): 103 | return (nr >> _IOC_DIRSHIFT) & _IOC_DIRMASK 104 | 105 | 106 | def _IOC_NR(nr): 107 | return (nr >> _IOC_NRSHIFT) & _IOC_NRMASK 108 | 109 | 110 | def _IOC_SIZE(nr): 111 | return (nr >> _IOC_SIZESHIFT) & _IOC_SIZEMASK 112 | 113 | 114 | def _IOC_TYPE(nr): 115 | return (nr >> _IOC_TYPESHIFT) & _IOC_TYPEMASK 116 | 117 | 118 | def _IOWR(type, nr, size): 119 | return _IOC(_IOC_READ | _IOC_WRITE, type, nr, sizeof(size)) 120 | 121 | __ELF__ = 1 122 | __i386 = 1 123 | __i386__ = 1 124 | __linux = 1 125 | __linux__ = 1 126 | __unix = 1 127 | __unix__ = 1 128 | i386 = 1 129 | linux = 1 130 | unix = 1 131 | 132 | # Constants from 133 | 134 | PARPORT_CONTROL_STROBE = 0x1 135 | PARPORT_CONTROL_AUTOFD = 0x2 136 | PARPORT_CONTROL_INIT = 0x4 137 | PARPORT_CONTROL_SELECT = 0x8 138 | PARPORT_STATUS_ERROR = 8 139 | PARPORT_STATUS_SELECT = 0x10 140 | PARPORT_STATUS_PAPEROUT = 0x20 141 | PARPORT_STATUS_ACK = 0x40 142 | PARPORT_STATUS_BUSY = 0x80 143 | 144 | IEEE1284_MODE_NIBBLE = 0 145 | IEEE1284_MODE_BYTE = 1 146 | IEEE1284_MODE_COMPAT = 1 << 8 147 | IEEE1284_MODE_BECP = 1 << 9 148 | IEEE1284_MODE_ECP = 1 << 4 149 | IEEE1284_MODE_ECPRLE = IEEE1284_MODE_ECP | (1 << 5) 150 | IEEE1284_MODE_ECPSWE = 1 << 10 151 | IEEE1284_MODE_EPP = 1 << 6 152 | IEEE1284_MODE_EPPSL = 1 << 11 153 | IEEE1284_MODE_EPPSWE = 1 << 12 154 | IEEE1284_DEVICEID = 1 << 2 155 | IEEE1284_EXT_LINK = 1 << 14 156 | 157 | IEEE1284_ADDR = 1 << 13 158 | IEEE1284_DATA = 0 159 | 160 | PARPORT_EPP_FAST = 1 161 | PARPORT_W91284PIC = 2 162 | 163 | 164 | class Parallel: 165 | """Class for controlling the pins on a parallel port 166 | 167 | This class provides bit-level access to the pins on a PC parallel 168 | port. It is primarily designed for programs which must control 169 | special circuitry - most often non-IEEE-1284-compliant devices 170 | other than printers - using 'bit-banging' techniques. 171 | 172 | The current implementation makes ioctl() calls to the Linux ppdev 173 | driver, using the Python fcntl library. It might be rewritten in 174 | C for extra speed. This particular implementation is written for 175 | Linux; all of the upper-level calls can be ported to Windows as 176 | well. 177 | 178 | On Linux, the ppdev device driver, from the Linux 2.4 parallel 179 | port subsystem, is used to control the parallel port hardware. 180 | This driver must be made available from a kernel compile. The 181 | option is called "Support user-space parallel-port drivers". When 182 | using the module, be sure to unload the lp module first: usually 183 | the lp module claims exclusive access to the parallel port, and if 184 | it is loaded, this class will fail to open the parallel port file, 185 | and throw an exception. 186 | 187 | The primary source of information about the Linux 2.4 parallel 188 | port subsystem is Tim Waugh's documentation, the source for which 189 | is available in the kernel tree. This document (called, 190 | appropriately enough, "The Linux 2.4 Parallel Port Subsystem"), 191 | thoroughly describes the parallel port drivers and how to use 192 | them. 193 | 194 | This class provides a method for each of the ioctls supported by 195 | the ppdev module. The ioctl methods are named, in uppercase, the 196 | same as the ioctls they invoke. The documentation for these 197 | methods was taken directly from the documentation for their 198 | corresponding ioctl, and modified only where necessary. 199 | 200 | Unless you have special reason to use the Linux ioctls, you should 201 | use instead the upper-level functions, which are named in 202 | lowerCase fashion and should be portable between Linux and 203 | Windows. This way, any code you write for this class will (or 204 | should) also work with the Windows version of this class. 205 | 206 | """ 207 | def __init__(self, port=0): 208 | if isinstance(port, int): 209 | self.device = "/dev/parport%d" % port 210 | else: 211 | self.device = port 212 | self._fd = None 213 | self._fd = os.open(self.device, os.O_RDWR) 214 | try: 215 | self.PPEXCL() 216 | self.PPCLAIM() 217 | self.setDataDir(1) 218 | self.setData(0) 219 | except IOError: 220 | os.close(self._fd) 221 | self._fd = None 222 | raise 223 | 224 | def __del__(self): 225 | if self._fd is not None: 226 | self.PPRELEASE() 227 | os.close(self._fd) 228 | 229 | def timevalToFloat(self, timeval): 230 | t = struct.unpack('ll', timeval) 231 | return t[0] + (t[1]/1000000.0) 232 | 233 | def floatToTimeval(self, time): 234 | sec = int(time) 235 | usec = int(time*1000000.0) 236 | return struct.pack('ll', sec, usec) 237 | 238 | def PPCLAIM(self): 239 | """ 240 | Claims access to the port. As a user-land device driver 241 | writer, you will need to do this before you are able to 242 | actually change the state of the parallel port in any 243 | way. Note that some operations only affect the ppdev driver 244 | and not the port, such as PPSETMODE; they can be performed 245 | while access to the port is not claimed. 246 | """ 247 | fcntl.ioctl(self._fd, PPCLAIM) 248 | 249 | def PPEXCL(self): 250 | """ 251 | Instructs the kernel driver to forbid any sharing of the port 252 | with other drivers, i.e. it requests exclusivity. The PPEXCL 253 | command is only valid when the port is not already claimed for 254 | use, and it may mean that the next PPCLAIM ioctl will fail: 255 | some other driver may already have registered itself on that 256 | port. 257 | 258 | Most device drivers don't need exclusive access to the 259 | port. It's only provided in case it is really needed, for 260 | example for devices where access to the port is required for 261 | extensive periods of time (many seconds). 262 | 263 | Note that the PPEXCL ioctl doesn't actually claim the port 264 | there and then---action is deferred until the PPCLAIM ioctl is 265 | performed. 266 | """ 267 | fcntl.ioctl(self._fd, PPEXCL) 268 | 269 | def PPRELEASE(self): 270 | """ 271 | Releases the port. Releasing the port undoes the effect of 272 | claiming the port. It allows other device drivers to talk to 273 | their devices (assuming that there are any). 274 | """ 275 | fcntl.ioctl(self._fd, PPRELEASE) 276 | 277 | def PPYIELD(self): 278 | """ 279 | Yields the port to another driver. This ioctl is a kind of 280 | short-hand for releasing the port and immediately reclaiming 281 | it. It gives other drivers a chance to talk to their devices, 282 | but afterwards claims the port back. An example of using this 283 | would be in a user-land printer driver: once a few characters 284 | have been written we could give the port to another device 285 | driver for a while, but if we still have characters to send to 286 | the printer we would want the port back as soon as possible. 287 | 288 | It is important not to claim the parallel port for too long, 289 | as other device drivers will have no time to service their 290 | devices. If your device does not allow for parallel port 291 | sharing at all, it is better to claim the parallel port 292 | exclusively (see PPEXCL). 293 | """ 294 | fcntl.ioctl(self._fd, PPYIELD) 295 | 296 | def PPNEGOT(self, mode): 297 | """ 298 | Performs IEEE 1284 negotiation into a particular 299 | mode. Briefly, negotiation is the method by which the host and 300 | the peripheral decide on a protocol to use when transferring 301 | data. 302 | 303 | An IEEE 1284 compliant device will start out in compatibility 304 | mode, and then the host can negotiate to another mode (such as 305 | ECP). 306 | 307 | The 'mode' parameter should be one of the following constants 308 | from PPDEV: 309 | 310 | - IEEE1284_MODE_COMPAT 311 | - IEEE1284_MODE_NIBBLE 312 | - IEEE1284_MODE_BYTE 313 | - IEEE1284_MODE_EPP 314 | - IEEE1284_MODE_ECP 315 | 316 | The PPNEGOT ioctl actually does two things: it performs the 317 | on-the-wire negotiation, and it sets the behaviour of 318 | subsequent read/write calls so that they use that mode (but 319 | see PPSETMODE). 320 | """ 321 | fcntl.ioctl(self._fd, PPNEGOT, struct.pack('i', mode)) 322 | 323 | def PPSETMODE(self, mode): 324 | """ 325 | Sets which IEEE 1284 protocol to use for the read and write 326 | calls. 327 | 328 | The 'mode' parameter should be one of the following constants 329 | from PPDEV: 330 | 331 | - IEEE1284_MODE_COMPAT 332 | - IEEE1284_MODE_NIBBLE 333 | - IEEE1284_MODE_BYTE 334 | - IEEE1284_MODE_EPP 335 | - IEEE1284_MODE_ECP 336 | """ 337 | fcntl.ioctl(self._fd, PPSETMODE, struct.pack('i', mode)) 338 | 339 | def PPGETMODE(self): 340 | """ 341 | Retrieves the IEEE 1284 mode being used for read and 342 | write. The return value is one of the following constants 343 | from PPDEV: 344 | 345 | - IEEE1284_MODE_COMPAT 346 | - IEEE1284_MODE_NIBBLE 347 | - IEEE1284_MODE_BYTE 348 | - IEEE1284_MODE_EPP 349 | - IEEE1284_MODE_ECP 350 | """ 351 | ret = struct.pack('i', 0) 352 | ret = fcntl.ioctl(self._fd, PPGETMODE, ret) 353 | return struct.unpack('i', ret)[0] 354 | 355 | def PPGETTIME(self): 356 | """ 357 | Retrieves the time-out value. The read and write calls will 358 | time out if the peripheral doesn't respond quickly enough. The 359 | PPGETTIME ioctl retrieves the length of time that the 360 | peripheral is allowed to have before giving up. 361 | 362 | Returns the timeout value in seconds as a floating-point value. 363 | """ 364 | ret = struct.pack('ll', 0, 0) 365 | ret = fcntl.ioctl(self._fd, PPGETTIME, ret) 366 | return self.timevalToFloat(ret) 367 | 368 | def PPSETTIME(self, time): 369 | """ 370 | Sets the time-out (see PPGETTIME for more information). 371 | 'time' is the new time-out in seconds; floating-point values 372 | are acceptable. 373 | """ 374 | fcntl.ioctl(self._fd, PPSETTIME, self.floatToTimeval(time)) 375 | 376 | def PPGETMODES(self): 377 | """ 378 | Retrieves the capabilities of the hardware (i.e. the modes 379 | field of the parport structure). 380 | """ 381 | raise NotImplementedError 382 | 383 | def PPSETFLAGS(self): 384 | """ 385 | Sets flags on the ppdev device which can affect future I/O 386 | operations. Available flags are: 387 | 388 | - PP_FASTWRITE 389 | - PP_FASTREAD 390 | - PP_W91284PIC 391 | """ 392 | raise NotImplementedError 393 | 394 | def PPWCONTROL(self, lines): 395 | """ 396 | Sets the control lines. The 'lines' parameter is a bitwise OR 397 | of the following constants from PPDEV: 398 | 399 | - PARPORT_CONTROL_STROBE 400 | - PARPORT_CONTROL_AUTOFD 401 | - PARPORT_CONTROL_INIT 402 | - PARPORT_CONTROL_SELECT 403 | """ 404 | fcntl.ioctl(self._fd, PPWCONTROL, struct.pack('B', lines)) 405 | 406 | def PPRCONTROL(self): 407 | """ 408 | Returns the last value written to the control register, in the 409 | form of an integer, for which each bit corresponds to a control 410 | line (although some are unused). 411 | 412 | This doesn't actually touch the hardware; the last value 413 | written is remembered in software. This is because some 414 | parallel port hardware does not offer read access to the 415 | control register. 416 | 417 | The control lines bits are defined by the following constants 418 | from PPDEV: 419 | 420 | - PARPORT_CONTROL_STROBE 421 | - PARPORT_CONTROL_AUTOFD 422 | - PARPORT_CONTROL_SELECT 423 | - PARPORT_CONTROL_INIT 424 | """ 425 | ret = struct.pack('B', 0) 426 | ret = fcntl.ioctl(self._fd, PPRCONTROL, ret) 427 | return struct.unpack('B', ret)[0] 428 | 429 | def PPFCONTROL(self, mask, val): 430 | """ 431 | Frobs the control lines. Since a common operation is to change 432 | one of the control signals while leaving the others alone, it 433 | would be quite inefficient for the user-land driver to have to 434 | use PPRCONTROL, make the change, and then use PPWCONTROL. Of 435 | course, each driver could remember what state the control 436 | lines are supposed to be in (they are never changed by 437 | anything else), but in order to provide PPRCONTROL, ppdev must 438 | remember the state of the control lines anyway. 439 | 440 | The PPFCONTROL ioctl is for "frobbing" control lines, and is 441 | like PPWCONTROL but acts on a restricted set of control 442 | lines. The ioctl parameter is a pointer to a struct 443 | ppdev_frob_struct: 444 | 445 | struct ppdev_frob_struct { 446 | unsigned char mask; 447 | unsigned char val; 448 | }; 449 | 450 | The mask and val fields are bitwise ORs of control line names 451 | (such as in PPWCONTROL). The operation performed by PPFCONTROL 452 | is: 453 | 454 | new_ctr = (old_ctr & ~mask) | val 455 | 456 | In other words, the signals named in mask are set to the 457 | values in val. 458 | """ 459 | fcntl.ioctl(self._fd, PPFCONTROL, struct.pack('BB', mask, val)) 460 | 461 | def PPRSTATUS(self): 462 | """ 463 | Returns an unsigned char containing bits set for each status 464 | line that is set (for instance, PARPORT_STATUS_BUSY). The 465 | ioctl parameter should be a pointer to an unsigned char. 466 | """ 467 | ret = struct.pack('B', 0) 468 | ret = fcntl.ioctl(self._fd, PPRSTATUS, ret) 469 | return struct.unpack('B', ret)[0] 470 | 471 | def PPDATADIR(self, out): 472 | """ 473 | Controls the data line drivers. Normally the computer's 474 | parallel port will drive the data lines, but for byte-wide 475 | transfers from the peripheral to the host it is useful to turn 476 | off those drivers and let the peripheral drive the 477 | signals. (If the drivers on the computer's parallel port are 478 | left on when this happens, the port might be damaged.) 479 | This is only needed in conjunction with PPWDATA or PPRDATA. 480 | 481 | The 'out' parameter indicates the desired port direction. If 482 | 'out' is true or non-zero, the drivers are turned on (forward 483 | direction); otherwise, the drivers are turned off (reverse 484 | direction). 485 | """ 486 | if out: 487 | msg = struct.pack('i', 0) 488 | else: 489 | msg = struct.pack('i', 1) 490 | fcntl.ioctl(self._fd, PPDATADIR, msg) 491 | 492 | def PPWDATA(self, byte): 493 | """ 494 | Sets the data lines (if in forward mode). The ioctl parameter 495 | is a pointer to an unsigned char. 496 | """ 497 | fcntl.ioctl(self._fd, PPWDATA, struct.pack('B', byte)) 498 | 499 | def PPRDATA(self): 500 | """ 501 | Reads the data lines (if in reverse mode). The ioctl parameter 502 | is a pointer to an unsigned char. 503 | """ 504 | ret = struct.pack('B', 0) 505 | ret = fcntl.ioctl(self._fd, PPRDATA, ret) 506 | return struct.unpack('B', ret)[0] 507 | 508 | def PPCLRIRQ(self): 509 | """ 510 | Returns the current interrupt count, and clears it. The ppdev 511 | driver keeps a count of interrupts as they are triggered. 512 | """ 513 | ret = struct.pack('i', 0) 514 | ret = fcntl.ioctl(self._fd, PPCLRIRQ, ret) 515 | return struct.unpack('i', ret)[0] 516 | 517 | def PPWCTLONIRQ(self, lines): 518 | """ 519 | Set a trigger response. Afterwards when an interrupt is 520 | triggered, the interrupt handler will set the control lines as 521 | requested. The ioctl parameter is a pointer to an unsigned 522 | char, which is interpreted in the same way as for PPWCONTROL. 523 | 524 | The reason for this ioctl is simply speed. Without this ioctl, 525 | responding to an interrupt would start in the interrupt 526 | handler, switch context to the user-land driver via poll or 527 | select, and then switch context back to the kernel in order to 528 | handle PPWCONTROL. Doing the whole lot in the interrupt 529 | handler is a lot faster. 530 | """ 531 | fcntl.ioctl(self._fd, PPWCTLONIRQ, struct.pack('B', lines)) 532 | 533 | # data lines 534 | # def data(self): 535 | # """Returns the states of the data bus line drivers (pins 2-9)""" 536 | # return self._data 537 | 538 | def setDataDir(self, out): 539 | """Activates or deactivates the data bus line drivers (pins 2-9)""" 540 | self._dataDir = out 541 | self.PPDATADIR(out) 542 | 543 | def dataDir(self): 544 | """Returns true if the data bus line drivers are on (pins 2-9)""" 545 | return self._dataDir 546 | 547 | # control lines 548 | # def strobe(self): 549 | # """Returns the state of the nStrobe output (pin 1)""" 550 | # return (self.PPRCONTROL()&PARPORT_CONTROL_STROBE)==0 551 | 552 | def setDataStrobe(self, level): 553 | """Sets the state of the nStrobe output (pin 1)""" 554 | if level: 555 | self.PPFCONTROL(PARPORT_CONTROL_STROBE, 0) 556 | else: 557 | self.PPFCONTROL(PARPORT_CONTROL_STROBE, PARPORT_CONTROL_STROBE) 558 | 559 | # def autoFd(self): 560 | # """Returns the state of the nAutoFd output (pin 14)""" 561 | # return (self.PPRCONTROL()&PARPORT_CONTROL_AUTOFD)==0 562 | 563 | def setAutoFeed(self, level): 564 | """Sets the state of the nAutoFd output (pin 14)""" 565 | if level: 566 | self.PPFCONTROL(PARPORT_CONTROL_AUTOFD, 0) 567 | else: 568 | self.PPFCONTROL(PARPORT_CONTROL_AUTOFD, PARPORT_CONTROL_AUTOFD) 569 | 570 | # def init(self): 571 | # """Returns the state of the nInit output (pin 16)""" 572 | # return (self.PPRCONTROL()&PARPORT_CONTROL_INIT)!=0 573 | 574 | def setInitOut(self, level): 575 | """Sets the state of the nInit output (pin 16)""" 576 | if level: 577 | self.PPFCONTROL(PARPORT_CONTROL_INIT, PARPORT_CONTROL_INIT) 578 | else: 579 | self.PPFCONTROL(PARPORT_CONTROL_INIT, 0) 580 | 581 | # def selectIn(self): 582 | # """Returns the state of the nSelectIn output (pin 17)""" 583 | # return (self.PPRCONTROL()&PARPORT_CONTROL_SELECT)==0 584 | 585 | def setSelect(self, level): 586 | """Sets the state of the nSelectIn output (pin 17)""" 587 | if level: 588 | self.PPFCONTROL(PARPORT_CONTROL_SELECT, 0) 589 | else: 590 | self.PPFCONTROL(PARPORT_CONTROL_SELECT, PARPORT_CONTROL_SELECT) 591 | 592 | def setData(self, d): 593 | """Sets the states of the data bus line drivers (pins 2-9)""" 594 | self._data = d 595 | return self.PPWDATA(d) 596 | 597 | def getData(self): 598 | """Gets the states of the data bus line (pin 2-9)""" 599 | return self.PPRDATA() 600 | 601 | # status lines 602 | def getInError(self): 603 | """Returns the level on the nFault pin (15)""" 604 | return (self.PPRSTATUS() & PARPORT_STATUS_ERROR) != 0 605 | 606 | def getInSelected(self): 607 | """Returns the level on the Select pin (13)""" 608 | return (self.PPRSTATUS() & PARPORT_STATUS_SELECT) != 0 609 | 610 | def getInPaperOut(self): 611 | """Returns the level on the paperOut pin (12)""" 612 | return (self.PPRSTATUS() & PARPORT_STATUS_PAPEROUT) != 0 613 | 614 | def getInAcknowledge(self): 615 | """Returns the level on the nAck pin (10)""" 616 | return (self.PPRSTATUS() & PARPORT_STATUS_ACK) != 0 617 | 618 | def getInBusy(self): 619 | """Returns the level on the Busy pin (11)""" 620 | return (self.PPRSTATUS() & PARPORT_STATUS_BUSY) == 0 621 | -------------------------------------------------------------------------------- /parallel/parallelutil.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | # 3 | # (C) 2002 Chris Liechti 4 | # 5 | # SPDX-License-Identifier: BSD-3-Clause 6 | 7 | 8 | class BitaccessMeta(type): 9 | """meta class that adds bit access properties to a 10 | parallel port implementation""" 11 | 12 | def __new__(self, classname, bases, classdict): 13 | klass = type.__new__(self, classname, bases, classdict) 14 | # status lines 15 | klass.paperOut = property(klass.getInPaperOut, None, "Read the PaperOut signal") 16 | # control lines 17 | klass.dataStrobe = property(None, klass.setDataStrobe, "Set the DataStrobe signal") 18 | # XXX ... other bits 19 | # data bits 20 | for bit in range(8): 21 | mask = 1 << bit 22 | 23 | def getter(self, mask=mask): 24 | return (self.getData() & mask) != 0 25 | 26 | def setter(self, b, mask=mask): 27 | if b: 28 | self.setData(self.getData() | mask) 29 | else: 30 | self.setData(self.getData() & ~mask) 31 | 32 | setattr(klass, "D%d" % bit, property(getter, setter, "Access databit %d" % bit)) 33 | 34 | # nibbles 35 | for name, shift, width in [('D0_D3', 0, 4), ('D4_D7', 4, 4)]: 36 | mask = (1 << width) - 1 37 | 38 | def getter(self, shift=shift, mask=mask): 39 | return (self.getData() >> shift) & mask 40 | 41 | def setter(self, b, shift=shift, mask=mask): 42 | self.setData((self.getData() & ~(mask << shift)) | ((b & mask) << shift)) 43 | 44 | setattr(klass, name, property(getter, setter, "Access to %s" % name)) 45 | return klass 46 | 47 | 48 | class VirtualParallelPort: 49 | """provides a virtual parallel port implementation, useful 50 | for tests and simulations without real hardware""" 51 | 52 | __metaclass__ = BitaccessMeta 53 | 54 | def __init__(self, port=None): 55 | self._data = 0 56 | 57 | def setData(self, value): 58 | self._data = value 59 | 60 | def getData(self): 61 | return self._data 62 | 63 | # inputs return dummy value 64 | def getInPaperOut(self): 65 | return self._dummy 66 | 67 | # outputs just store a tuple with (action, value) pair 68 | def setDataStrobe(self, value): 69 | self._last = ('setDataStrobe', value) 70 | 71 | 72 | # testing 73 | if __name__ == '__main__': 74 | import unittest 75 | import sys 76 | 77 | class TestBitaccess(unittest.TestCase): 78 | """Tests a port with no timeout""" 79 | def setUp(self): 80 | self.p = VirtualParallelPort() 81 | 82 | def testDatabits(self): 83 | """bit by bit D0..D7""" 84 | p = self.p 85 | p.D0 = p.D2 = p.D4 = p.D6 = 1 86 | self.failUnlessEqual(p._data, 0x55) 87 | self.failUnlessEqual( 88 | [p.D7, p.D6, p.D5, p.D4, p.D3, p.D2, p.D1, p.D0], 89 | [0, 1, 0, 1, 0, 1, 0, 1] 90 | ) 91 | p._data <<= 1 92 | self.failUnlessEqual( 93 | [p.D7, p.D6, p.D5, p.D4, p.D3, p.D2, p.D1, p.D0], 94 | [1, 0, 1, 0, 1, 0, 1, 0] 95 | ) 96 | 97 | def testDatabitsGroups(self): 98 | """nibbles D0..D7""" 99 | p = self.p 100 | p.D0_D3 = 14 101 | self.failUnlessEqual(p._data, 0x0e) 102 | p.D0_D3 = 0 103 | p.D4_D7 = 13 104 | self.failUnlessEqual(p._data, 0xd0) 105 | p.D0_D3 = p.D4_D7 = 0xa 106 | self.failUnlessEqual(p._data, 0xaa) 107 | # test bit patterns 108 | for x in range(256): 109 | # test getting 110 | p._data = x 111 | self.failUnlessEqual((p.D4_D7, p.D0_D3), (((x >> 4) & 0xf), (x & 0xf))) 112 | # test setting 113 | p._data = 0 114 | (p.D4_D7, p.D0_D3) = (((x >> 4) & 0xf), (x & 0xf)) 115 | self.failUnlessEqual(p._data, x) 116 | 117 | def testStatusbits(self): 118 | """bit by bit status lines""" 119 | # read the property: 120 | self.p._dummy = 0 121 | self.failUnlessEqual(self.p.paperOut, 0) 122 | 123 | self.p._dummy = 1 124 | self.failUnlessEqual(self.p.paperOut, 1) 125 | 126 | # read only, must not be writable: 127 | self.failUnlessRaises(AttributeError, setattr, self.p, 'paperOut', 1) 128 | 129 | def testControlbits(self): 130 | """bit by bit control lines""" 131 | self.p.dataStrobe = 0 132 | self.failUnlessEqual(self.p._last, ('setDataStrobe', 0)) 133 | self.p.dataStrobe = 1 134 | self.failUnlessEqual(self.p._last, ('setDataStrobe', 1)) 135 | 136 | # write only, must not be writable: 137 | self.failUnlessRaises(AttributeError, getattr, self.p, 'dataStrobe') 138 | 139 | sys.argv.append('-v') 140 | # When this module is executed from the command-line, it runs all its tests 141 | unittest.main() 142 | -------------------------------------------------------------------------------- /parallel/parallelwin32.py: -------------------------------------------------------------------------------- 1 | # pyparallel driver for win32 2 | # see __init__.py 3 | # 4 | # (C) 2002 Chris Liechti 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | # 8 | # thanks to Dincer Aydin dinceraydin@altavista.net for his work on the 9 | # winioport module: www.geocities.com/dinceraydin/ the graphic below is 10 | # borrowed form him ;-) 11 | 12 | 13 | # LPT1 = 0x0378 or 0x03BC 14 | # LPT2 = 0x0278 or 0x0378 15 | # LPT3 = 0x0278 16 | # 17 | # Data Register (base + 0) ........ outputs 18 | # 19 | # 7 6 5 4 3 2 1 0 20 | # . . . . . . . * D0 ........... (pin 2), 1=High, 0=Low (true) 21 | # . . . . . . * . D1 ........... (pin 3), 1=High, 0=Low (true) 22 | # . . . . . * . . D2 ........... (pin 4), 1=High, 0=Low (true) 23 | # . . . . * . . . D3 ........... (pin 5), 1=High, 0=Low (true) 24 | # . . . * . . . . D4 ........... (pin 6), 1=High, 0=Low (true) 25 | # . . * . . . . . D5 ........... (pin 7), 1=High, 0=Low (true) 26 | # . * . . . . . . D6 ........... (pin 8), 1=High, 0=Low (true) 27 | # * . . . . . . . D7 ........... (pin 9), 1=High, 0=Low (true) 28 | # 29 | # Status Register (base + 1) ...... inputs 30 | # 31 | # 7 6 5 4 3 2 1 0 32 | # . . . . . * * * Undefined 33 | # . . . . * . . . Error ........ (pin 15), high=1, low=0 (true) 34 | # . . . * . . . . Selected ..... (pin 13), high=1, low=0 (true) 35 | # . . * . . . . . No paper ..... (pin 12), high=1, low=0 (true) 36 | # . * . . . . . . Ack .......... (pin 10), high=1, low=0 (true) 37 | # * . . . . . . . Busy ......... (pin 11), high=0, low=1 (inverted) 38 | # 39 | # ctrl Register (base + 2) ..... outputs 40 | # 41 | # 7 6 5 4 3 2 1 0 42 | # . . . . . . . * Strobe ....... (pin 1), 1=low, 0=high (inverted) 43 | # . . . . . . * . Auto Feed .... (pin 14), 1=low, 0=high (inverted) 44 | # . . . . . * . . Initialize ... (pin 16), 1=high,0=low (true) 45 | # . . . . * . . . Select ....... (pin 17), 1=low, 0=high (inverted) 46 | # * * * * . . . . Unused 47 | import ctypes 48 | import os 49 | 50 | LPT1 = 0 51 | LPT2 = 1 52 | 53 | LPT1_base = 0x0378 54 | LPT2_base = 0x0278 55 | 56 | # need to patch PATH so that the DLL can be found and loaded 57 | dll_path = os.path.abspath(os.path.dirname(__file__)) + '\\win32' 58 | os.environ['PATH'] = os.environ['PATH'] + ';' + dll_path 59 | # fake module, names of the functions are the same as in the old _pyparallel 60 | # python extension in earlier versions of this modules 61 | _pyparallel = ctypes.windll.simpleio 62 | # need to initialize giveio on WinNT based systems 63 | if _pyparallel.init(): 64 | raise IOError('Could not access the giveio driver which is required on NT based systems.') 65 | 66 | 67 | class Parallel: 68 | def __init__(self, port=LPT1): 69 | if port == LPT1: 70 | self.dataRegAdr = LPT1_base 71 | elif port == LPT2: 72 | self.dataRegAdr = LPT2_base 73 | else: 74 | raise ValueError("No such port available - expecting a number") 75 | self.statusRegAdr = self.dataRegAdr + 1 76 | self.ctrlRegAdr = self.dataRegAdr + 2 77 | self.ctrlReg = _pyparallel.inp(self.ctrlRegAdr) 78 | 79 | def setData(self, value): 80 | _pyparallel.outp(self.dataRegAdr, value) 81 | 82 | def setDataDir(self, level): 83 | """set for port as input, clear for output""" 84 | if level: 85 | self.ctrlReg |= 0x20 86 | else: 87 | self.ctrlReg &= ~0x20 88 | _pyparallel.outp(self.ctrlRegAdr, self.ctrlReg) 89 | 90 | # control register output functions 91 | def setDataStrobe(self, level): 92 | """data strobe bit""" 93 | if level: 94 | self.ctrlReg = self.ctrlReg & ~0x01 95 | else: 96 | self.ctrlReg = self.ctrlReg | 0x01 97 | _pyparallel.outp(self.ctrlRegAdr, self.ctrlReg) 98 | 99 | def setAutoFeed(self, level): 100 | """auto feed bit""" 101 | if level: 102 | self.ctrlReg = self.ctrlReg & ~0x02 103 | else: 104 | self.ctrlReg = self.ctrlReg | 0x02 105 | _pyparallel.outp(self.ctrlRegAdr, self.ctrlReg) 106 | 107 | def setInitOut(self, level): 108 | """initialize bit""" 109 | if level: 110 | self.ctrlReg = self.ctrlReg | 0x04 111 | else: 112 | self.ctrlReg = self.ctrlReg & ~0x04 113 | _pyparallel.outp(self.ctrlRegAdr, self.ctrlReg) 114 | 115 | def setSelect(self, level): 116 | """select bit""" 117 | if level: 118 | self.ctrlReg = self.ctrlReg & ~0x08 119 | else: 120 | self.ctrlReg = self.ctrlReg | 0x08 121 | _pyparallel.outp(self.ctrlRegAdr, self.ctrlReg) 122 | 123 | def getInError(self): 124 | """Error pin""" 125 | return _pyparallel.inp(self.statusRegAdr) & 0x08 and 1 126 | 127 | def getInSelected(self): 128 | """select pin""" 129 | return _pyparallel.inp(self.statusRegAdr) & 0x10 and 1 130 | 131 | def getInPaperOut(self): 132 | """paper out pin""" 133 | return _pyparallel.inp(self.statusRegAdr) & 0x20 and 1 134 | 135 | def getInAcknowledge(self): 136 | """Acknowledge pin""" 137 | return _pyparallel.inp(self.statusRegAdr) & 0x40 and 1 138 | 139 | def getInBusy(self): 140 | """input from busy pin""" 141 | return not (_pyparallel.inp(self.statusRegAdr) & 0x80) 142 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # setup.py 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | import os 8 | import sys 9 | 10 | # windows installer: 11 | # python setup.py bdist_wininst 12 | 13 | # patch distutils if it can't cope with the "classifiers" or 14 | # "download_url" keywords 15 | if sys.version < '2.2.3': 16 | from distutils.dist import DistributionMetadata 17 | DistributionMetadata.classifiers = None 18 | DistributionMetadata.download_url = None 19 | 20 | 21 | def read(fname): 22 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 23 | 24 | 25 | setup( 26 | name='pyparallel', 27 | description='Python Parallel Port Extension', 28 | version='0.2.2', 29 | author='Chris Liechti', 30 | author_email='cliechti@gmx.net', 31 | url='https://github.com/pyserial/pyparallel', 32 | packages=['parallel', 'parallel.win32'], 33 | package_dir={'parallel': 'parallel', 34 | 'parallel.win32': 'src/win32'}, 35 | setup_requires=[ 36 | "flake8" 37 | ], 38 | license='BSD', 39 | long_description=read('README.rst'), 40 | keywords='parallel port parport lpt printer ppdev', 41 | classifiers=[ 42 | 'Development Status :: 4 - Beta', 43 | 'Intended Audience :: Developers', 44 | 'License :: OSI Approved :: BSD License', 45 | 'Natural Language :: English', 46 | 'Operating System :: POSIX', 47 | 'Operating System :: Microsoft :: Windows', 48 | 'Programming Language :: Python', 49 | 'Programming Language :: Python :: 3', 50 | 'Topic :: Communications', 51 | 'Topic :: Software Development :: Libraries', 52 | ], 53 | include_package_data=True 54 | ) 55 | -------------------------------------------------------------------------------- /src/win32/README.txt: -------------------------------------------------------------------------------- 1 | New: 2 | simpleio.c provides inp(), outp(), init() these functions are accessed trough 3 | ctypes. 4 | 5 | Build (cygwin/mingw): run "make" the makfile should care of everything. 6 | 7 | - - - - - - - - - - - - - 8 | Old python extension: 9 | 10 | This extension is needed on Windows as there is no API to 11 | manipulate the parallel port. This Python extension exposes 12 | "inp()" and "outp()" that can be used to manipulate the printer 13 | IOs directly. It could be basicaly used to access any IO port. 14 | 15 | On Windows NT/2k/XP direct access to IOs is not possible for 16 | user applications (only kernel mode drivers). Because of that 17 | a kernel driver is needed. The sources to GIVEIO.SYS are in 18 | the respective directory. The loaddrv sources come from the 19 | archive that is mentioned in the giveio readme. 20 | 21 | If the extension detects that it is running on an NT based system 22 | (NT, 2k, XP) it activates the giveio driver to gain access to the 23 | IO ports. To make this work, the giveio driver must be installed. 24 | this can be done with the loaddrv tool. The batchfiles 25 | "install_giveio.bat" and "remove_giveio.bat" do whats needed to 26 | install or uninstall. 27 | 28 | Thanks go to 29 | Dale Roberts for the giveio driver and to 30 | Paula Tomlinson for the loaddrv sources 31 | 32 | chris 33 | -------------------------------------------------------------------------------- /src/win32/_pyparallel.c: -------------------------------------------------------------------------------- 1 | // Parallel port extension for Win32 2 | // "inp" and "outp" are used to access the parallelport hardware 3 | // needs giveio.sys driver on NT/2k/XP 4 | // 5 | // (C) 2002 Chris Liechti 6 | // this is distributed under a free software license, see license.txt 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define DRIVERNAME "\\\\.\\giveio" 13 | 14 | /* module-functions */ 15 | 16 | static PyObject* 17 | py_outp(PyObject *self, PyObject *args) 18 | { 19 | int port, value; 20 | if(!PyArg_ParseTuple(args, "ii", &port, &value)) 21 | return 0; 22 | _outp(port, value); 23 | Py_INCREF(Py_None); 24 | return Py_None; 25 | } 26 | 27 | static PyObject* 28 | py_inp(PyObject *self, PyObject *args) 29 | { 30 | int port, value; 31 | if(!PyArg_ParseTuple(args, "i", &port)) 32 | return 0; 33 | value = _inp(port); 34 | return Py_BuildValue("i", value); 35 | } 36 | 37 | 38 | 39 | static PyMethodDef pypar_methods[] = { 40 | {"outp", py_outp, METH_VARARGS}, 41 | {"inp", py_inp, METH_VARARGS}, 42 | {0, 0} 43 | }; 44 | 45 | /* module entry-point (module-initialization) function */ 46 | void init_pyparallel(void) { 47 | OSVERSIONINFO vi; 48 | 49 | /* Create the module and add the functions */ 50 | Py_InitModule("_pyparallel", pypar_methods); 51 | 52 | //detect OS, on NT,2k,XP the driver needs to be loaded 53 | vi.dwOSVersionInfoSize = sizeof(vi); 54 | GetVersionEx(&vi); 55 | if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT) { 56 | HANDLE h; 57 | //try to open driver 58 | h = CreateFile(DRIVERNAME, GENERIC_READ, 0, NULL, 59 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 60 | if (h == INVALID_HANDLE_VALUE) { 61 | //if it fails again, then we have a problem... -> exception 62 | PyErr_Format(PyExc_ImportError, "Couldn't access giveio device"); 63 | } 64 | //close again immediately. 65 | //the process is now tagged to have the rights it needs, 66 | //the giveio driver remembers that 67 | if (h != NULL) CloseHandle(h); //close the driver's file 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/win32/giveio/GIVEIO.C: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | 3 | Author: Dale Roberts 4 | Date: 8/30/95 5 | Program: GIVEIO.SYS 6 | Compile: Use DDK BUILD facility 7 | 8 | Purpose: Give direct port I/O access to a user mode process. 9 | 10 | *********************************************************************/ 11 | #include 12 | 13 | /* 14 | * The name of our device driver. 15 | */ 16 | #define DEVICE_NAME_STRING L"giveio" 17 | 18 | /* 19 | * This is the "structure" of the IOPM. It is just a simple 20 | * character array of length 0x2000. 21 | * 22 | * This holds 8K * 8 bits -> 64K bits of the IOPM, which maps the 23 | * entire 64K I/O space of the x86 processor. Any 0 bits will give 24 | * access to the corresponding port for user mode processes. Any 1 25 | * bits will disallow I/O access to the corresponding port. 26 | */ 27 | #define IOPM_SIZE 0x2000 28 | typedef UCHAR IOPM[IOPM_SIZE]; 29 | 30 | /* 31 | * This will hold simply an array of 0's which will be copied 32 | * into our actual IOPM in the TSS by Ke386SetIoAccessMap(). 33 | * The memory is allocated at driver load time. 34 | */ 35 | IOPM *IOPM_local = 0; 36 | 37 | /* 38 | * These are the two undocumented calls that we will use to give 39 | * the calling process I/O access. 40 | * 41 | * Ke386IoSetAccessMap() copies the passed map to the TSS. 42 | * 43 | * Ke386IoSetAccessProcess() adjusts the IOPM offset pointer so that 44 | * the newly copied map is actually used. Otherwise, the IOPM offset 45 | * points beyond the end of the TSS segment limit, causing any I/O 46 | * access by the user mode process to generate an exception. 47 | */ 48 | void Ke386SetIoAccessMap(int, IOPM *); 49 | void Ke386QueryIoAccessMap(int, IOPM *); 50 | void Ke386IoSetAccessProcess(PEPROCESS, int); 51 | 52 | /********************************************************************* 53 | Release any allocated objects. 54 | *********************************************************************/ 55 | VOID GiveioUnload(IN PDRIVER_OBJECT DriverObject) 56 | { 57 | WCHAR DOSNameBuffer[] = L"\\DosDevices\\" DEVICE_NAME_STRING; 58 | UNICODE_STRING uniDOSString; 59 | 60 | if(IOPM_local) 61 | MmFreeNonCachedMemory(IOPM_local, sizeof(IOPM)); 62 | 63 | RtlInitUnicodeString(&uniDOSString, DOSNameBuffer); 64 | IoDeleteSymbolicLink (&uniDOSString); 65 | IoDeleteDevice(DriverObject->DeviceObject); 66 | } 67 | 68 | /********************************************************************* 69 | Set the IOPM (I/O permission map) of the calling process so that it 70 | is given full I/O access. Our IOPM_local[] array is all zeros, so 71 | the IOPM will be all zeros. If OnFlag is 1, the process is given I/O 72 | access. If it is 0, access is removed. 73 | *********************************************************************/ 74 | VOID SetIOPermissionMap(int OnFlag) 75 | { 76 | Ke386IoSetAccessProcess(PsGetCurrentProcess(), OnFlag); 77 | Ke386SetIoAccessMap(1, IOPM_local); 78 | } 79 | 80 | void GiveIO(void) 81 | { 82 | SetIOPermissionMap(1); 83 | } 84 | 85 | /********************************************************************* 86 | Service handler for a CreateFile() user mode call. 87 | 88 | This routine is entered in the driver object function call table by 89 | the DriverEntry() routine. When the user mode application calls 90 | CreateFile(), this routine gets called while still in the context of 91 | the user mode application, but with the CPL (the processor's Current 92 | Privelege Level) set to 0. This allows us to do kernel mode 93 | operations. GiveIO() is called to give the calling process I/O 94 | access. All the user mode application needs do to obtain I/O access 95 | is open this device with CreateFile(). No other operations are 96 | required. 97 | *********************************************************************/ 98 | NTSTATUS GiveioCreateDispatch( 99 | IN PDEVICE_OBJECT DeviceObject, 100 | IN PIRP Irp 101 | ) 102 | { 103 | GiveIO(); // give the calling process I/O access 104 | 105 | Irp->IoStatus.Information = 0; 106 | Irp->IoStatus.Status = STATUS_SUCCESS; 107 | IoCompleteRequest(Irp, IO_NO_INCREMENT); 108 | return STATUS_SUCCESS; 109 | } 110 | 111 | /********************************************************************* 112 | Driver Entry routine. 113 | 114 | This routine is called only once after the driver is initially 115 | loaded into memory. It allocates everything necessary for the 116 | driver's operation. In our case, it allocates memory for our IOPM 117 | array, and creates a device which user mode applications can open. 118 | It also creates a symbolic link to the device driver. This allows 119 | a user mode application to access our driver using the \\.\giveio 120 | notation. 121 | *********************************************************************/ 122 | NTSTATUS DriverEntry( 123 | IN PDRIVER_OBJECT DriverObject, 124 | IN PUNICODE_STRING RegistryPath 125 | ) 126 | { 127 | PDEVICE_OBJECT deviceObject; 128 | NTSTATUS status; 129 | WCHAR NameBuffer[] = L"\\Device\\" DEVICE_NAME_STRING; 130 | WCHAR DOSNameBuffer[] = L"\\DosDevices\\" DEVICE_NAME_STRING; 131 | UNICODE_STRING uniNameString, uniDOSString; 132 | 133 | // 134 | // Allocate a buffer for the local IOPM and zero it. 135 | // 136 | IOPM_local = MmAllocateNonCachedMemory(sizeof(IOPM)); 137 | if(IOPM_local == 0) 138 | return STATUS_INSUFFICIENT_RESOURCES; 139 | RtlZeroMemory(IOPM_local, sizeof(IOPM)); 140 | 141 | // 142 | // Set up device driver name and device object. 143 | // 144 | RtlInitUnicodeString(&uniNameString, NameBuffer); 145 | RtlInitUnicodeString(&uniDOSString, DOSNameBuffer); 146 | 147 | status = IoCreateDevice(DriverObject, 0, 148 | &uniNameString, 149 | FILE_DEVICE_UNKNOWN, 150 | 0, FALSE, &deviceObject); 151 | 152 | if(!NT_SUCCESS(status)) 153 | return status; 154 | 155 | status = IoCreateSymbolicLink (&uniDOSString, &uniNameString); 156 | 157 | if (!NT_SUCCESS(status)) 158 | return status; 159 | 160 | // 161 | // Initialize the Driver Object with driver's entry points. 162 | // All we require are the Create and Unload operations. 163 | // 164 | DriverObject->MajorFunction[IRP_MJ_CREATE] = GiveioCreateDispatch; 165 | DriverObject->DriverUnload = GiveioUnload; 166 | return STATUS_SUCCESS; 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/win32/giveio/MAKEFILE: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source 3 | # file to this component. This file merely indirects to the real make file 4 | # that is shared by all the driver components of the Windows NT DDK 5 | # 6 | 7 | !INCLUDE $(NTMAKEENV)\makefile.def 8 | -------------------------------------------------------------------------------- /src/win32/giveio/README.TXT: -------------------------------------------------------------------------------- 1 | The entire archive was pulled from here: 2 | Dr. Dobb's Journal, http://www.ddj.com/ftp/1996/1996.05/directio.zip 3 | It contained some other snippets too, but as they are not useful here 4 | I don't include them here. 5 | 6 | chris 7 | 8 | Following is the original readme of the archive: 9 | ----------------------------------------------------------------------------- 10 | 11 | 12 | Author: Dale Roberts, Direct I/O and Windows NT 13 | 14 | 15 | Here are two helpful hints to get you going with GIVEIO. The first 16 | section below mentions the INSTDRV utility that is provided with the 17 | Microsoft DDK. If you do not have access to the DDK, you can use Paula 18 | Tomlinson's program LOADDRV instead. She describes it in her May 1995 19 | article in Windows/DOS Developer's Journal (now Windows Developer's 20 | Journal). You can get the program from their FTP site at: 21 | 22 | ftp://ftp.mfi.com/pub/windev/1995/may95.zip. 23 | 24 | 25 | ------------------------------------------------------------------ 26 | Device Driver Installation Made Easy 27 | 28 | The Microsoft NT Device Driver Kit documentation implies in several 29 | places that there are several steps involved in installing a device driver 30 | and making it accessible to a Win32 application. It explains that you 31 | should edit the registry manually and then reboot the system. But 32 | device drivers are dynamically loadable and unloadable in NT, and the 33 | DDK comes with a very handy utility called INSTDRV that 34 | demonstrates this facility in a very practical manner. 35 | 36 | INSTDRV is a console application that will register, load, and start a 37 | kernel mode device driver. It does not require you to edit the registry 38 | manually or reboot the computer. On the command line you simply 39 | give the name of your device driver and the complete path to the .SYS 40 | file (which does not need to be in the system's DRIVERS directory). 41 | After this command is executed, you will find that the driver has been 42 | registered with the system and appears in the Devices applet in the 43 | control panel. If you give the word remove instead of the path, the 44 | driver is removed from the system and taken out of the driver database. 45 | 46 | Once the driver is loaded and started, you can use the control panel's 47 | Devices applet to start and stop it, or you can use the net start and net 48 | stop commands (these are much faster) from a console window. When 49 | a kernel mode device is stopped, it is in also unloaded from memory. 50 | The next time you start the device, a fresh copy of the driver is read 51 | from the hard drive, if it has been modified. This makes it very 52 | convenient to develop device drivers, since you can go through the 53 | modify, stop, start cycle repeatedly without ever needing to reboot. If 54 | you need your driver to load at boot time, you can go into the Devices 55 | applet and change its startup mode to boot. 56 | 57 | The other component that is needed to make the driver visible to user 58 | mode applications, so they can use CreateFile() calls to access the 59 | driver, is registering the name in the DOS Devices name space. This 60 | can be done, as documented in the DDK, by editing the registry 61 | manually and rebooting. Or, much more simply, the kernel mode 62 | driver can call the IoCreateSymbolicLink() function to register the 63 | name itself. The GIVEIO driver shown in Listing Four uses the later 64 | technique. Once the name is registered, user mode applications can get 65 | a file handle to the device driver by calling CreateFile() with the driver 66 | name as the file parameter, but preceding the driver name with the 67 | special cookie \\.\. The TESTIO application in Listing Five uses this 68 | technique. 69 | 70 | ------------------------------------------------------------------ 71 | Quick Trick: Using DEBUG With Port I/O 72 | 73 | Sometimes you just need to do a quick I/O operation to a port. In DOS, 74 | you could use the DEBUG program to accomplish this. In NT, once 75 | you have the GIVEIO device driver up and running, you can once 76 | again use DEBUG for port I/O. If you look at the source code for the 77 | test application, you'll see that all it does is open and close the GIVEIO 78 | device driver. It uses the special cookie \\.\ before the driver name in 79 | order to access it. Without modifying DEBUG, you can have it open 80 | this device driver by simply typing debug \\.\giveio in an NT console 81 | window. You will get an error message complaining that the file 82 | \\.\giveio is not found, but it will give DEBUG I/O access anyway. 83 | Subsequent DOS applications that are run from this console window 84 | will also have I/O access. 85 | 86 | WIN32 applications executed from this console window will still cause 87 | exceptions. This is because DEBUG (and any other DOS application) 88 | runs in the context of the VDM (Virtual DOS Machine) process of the 89 | console box, whereas each WIN32 application gets its own process. 90 | The VDM process stays active as long as the console window is open, 91 | but each WIN32 application creates a brand new process with the 92 | IOPM offset initialized to point beyond the end of the TSS. 93 | -------------------------------------------------------------------------------- /src/win32/giveio/SOURCES: -------------------------------------------------------------------------------- 1 | TARGETNAME=giveio 2 | TARGETPATH=. 3 | TARGETTYPE=DRIVER 4 | 5 | INCLUDES=c:\ddk\inc 6 | 7 | SOURCES=giveio.c 8 | -------------------------------------------------------------------------------- /src/win32/install_giveio.bat: -------------------------------------------------------------------------------- 1 | @set DIRVERNAME=giveio 2 | 3 | @echo Installing Windows NT/2k/XP driver: %DIRVERNAME% 4 | 5 | @loaddrv install %DIRVERNAME% 6 | @if errorlevel 3 goto error 7 | 8 | @loaddrv start %DIRVERNAME% 9 | @if errorlevel 1 goto error 10 | 11 | @loaddrv starttype %DIRVERNAME% auto 12 | @if errorlevel 1 goto error 13 | 14 | @goto exit 15 | :error 16 | @echo ERROR: Installation of %DIRVERNAME% failed 17 | :exit 18 | @pause 19 | -------------------------------------------------------------------------------- /src/win32/loaddrv_console/loaddrv.c: -------------------------------------------------------------------------------- 1 | // loaddrv.c - Dynamic driver install/start/stop/remove 2 | // based on Paula Tomlinson's LOADDRV program. 3 | // She describes it in her May 1995 article in Windows/DOS Developer's 4 | // Journal (now Windows Developer's Journal). 5 | // i removed the old/ugly dialog, it now accepts command line options and 6 | // prints error messages with textual description from the OS. 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "loaddrv.h" 13 | 14 | // globals 15 | SC_HANDLE hSCMan = NULL; 16 | 17 | //get ext messages for windows error codes: 18 | void DisplayErrorText(DWORD dwLastError) { 19 | LPSTR MessageBuffer; 20 | DWORD dwBufferLength; 21 | 22 | DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | 23 | FORMAT_MESSAGE_IGNORE_INSERTS | 24 | FORMAT_MESSAGE_FROM_SYSTEM; 25 | 26 | dwBufferLength = FormatMessageA( 27 | dwFormatFlags, 28 | NULL, // module to get message from (NULL == system) 29 | dwLastError, 30 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language 31 | (LPSTR) &MessageBuffer, 32 | 0, 33 | NULL 34 | ); 35 | if (dwBufferLength) { 36 | // Output message 37 | puts(MessageBuffer); 38 | // Free the buffer allocated by the system. 39 | LocalFree(MessageBuffer); 40 | } 41 | } 42 | 43 | int exists(char *filename) { 44 | FILE * pFile; 45 | pFile = fopen(filename, "r"); 46 | return pFile != NULL; 47 | } 48 | 49 | void usage(void) { 50 | printf("USGAE: loaddrv command drivername [args...]\n\n" 51 | "NT/2k/XP Driver and Service modification tool.\n" 52 | "(C)2002 Chris Liechti \n\n" 53 | "Suported commands:\n\n" 54 | " install [fullpathforinstall]\n" 55 | " Install new service. Loaded from given path. If path is not present,\n" 56 | " the local directory is searched for a .sys file. If the service\n" 57 | " already exists, it must be removed first.\n" 58 | " start\n" 59 | " Start service. It must be installed in advance.\n" 60 | " stop\n" 61 | " Stop service.\n" 62 | " remove\n" 63 | " Remove service. It must be stopped in advance.\n" 64 | " status\n" 65 | " Show status information about service.\n" 66 | " starttype auto|manual|system|disable\n" 67 | " Change startup type to the given type.\n" 68 | ); 69 | } 70 | 71 | int main(int argc, char *argv[]) { 72 | DWORD status = 0; 73 | int level = 0; 74 | if (argc < 3) { 75 | usage(); 76 | exit(1); 77 | } 78 | LoadDriverInit(); 79 | if (strcmp(argv[1], "start") == 0) { 80 | printf("starting %s... ", argv[2]); 81 | status = DriverStart(argv[2]); 82 | if ( status != OKAY) { 83 | printf("start failed (status %ld):\n", status); 84 | level = 1; 85 | } else { 86 | printf("ok.\n"); 87 | } 88 | } else if (strcmp(argv[1], "stop") == 0) { 89 | printf("stoping %s... ", argv[2]); 90 | status = DriverStop(argv[2]); 91 | if ( status != OKAY) { 92 | printf("stop failed (status %ld):\n", status); 93 | level = 1; 94 | } else { 95 | printf("ok.\n"); 96 | } 97 | } else if (strcmp(argv[1], "install") == 0) { 98 | char path[MAX_PATH*2]; 99 | if (argc<4) { 100 | char cwd[MAX_PATH]; 101 | getcwd(cwd, sizeof cwd); 102 | sprintf(path, "%s\\%s.sys", cwd, argv[2]); 103 | } else { 104 | strncpy(path, argv[3], MAX_PATH); 105 | } 106 | if (exists(path)) { 107 | printf("installing %s from %s... ", argv[2], path); 108 | status = DriverInstall(path, argv[2]); 109 | if ( status != OKAY) { 110 | printf("install failed (status %ld):\n", status); 111 | level = 2; 112 | } else { 113 | printf("ok.\n"); 114 | } 115 | } else { 116 | printf("install failed, file not found: %s\n", path); 117 | level = 1; 118 | } 119 | } else if (strcmp(argv[1], "remove") == 0) { 120 | printf("removing %s... ", argv[2]); 121 | status = DriverRemove(argv[2]); 122 | if ( status != OKAY) { 123 | printf("remove failed (status %ld):\n", status); 124 | level = 1; 125 | } else { 126 | printf("ok.\n"); 127 | } 128 | } else if (strcmp(argv[1], "status") == 0) { 129 | printf("status of %s:\n", argv[2]); 130 | status = DriverStatus(argv[2]); 131 | if ( status != OKAY) { 132 | printf("stat failed (status %ld):\n", status); 133 | level = 1; 134 | } else { 135 | printf("ok.\n"); 136 | } 137 | } else if (strcmp(argv[1], "starttype") == 0) { 138 | if (argc < 4) { 139 | printf("Error: need start type (string) as argument.\n"); 140 | level = 2; 141 | } else { 142 | DWORD type = 0; 143 | printf("set start type of %s to %s... ", argv[2], argv[3]); 144 | if (strcmp(argv[1], "boot") == 0) { 145 | type = SERVICE_BOOT_START; 146 | } else if (strcmp(argv[3], "system") == 0) { 147 | type = SERVICE_SYSTEM_START; 148 | } else if (strcmp(argv[3], "auto") == 0) { 149 | type = SERVICE_AUTO_START; 150 | } else if (strcmp(argv[3], "manual") == 0) { 151 | type = SERVICE_DEMAND_START; 152 | } else if (strcmp(argv[3], "disabled") == 0) { 153 | type = SERVICE_DISABLED; 154 | } else { 155 | printf("unknown type\n"); 156 | level = 1; 157 | } 158 | if (level == 0) { 159 | status = DriverStartType(argv[2], type); 160 | if ( status != OKAY) { 161 | printf("set start type failed (status %ld):\n", status); 162 | level = 1; 163 | } else { 164 | printf("ok.\n"); 165 | } 166 | } 167 | } 168 | } else { 169 | usage(); 170 | level = 1; 171 | } 172 | if (status) DisplayErrorText(status); 173 | LoadDriverCleanup(); 174 | exit(level); 175 | return 0; 176 | } 177 | 178 | 179 | DWORD LoadDriverInit(void) { 180 | // connect to local service control manager 181 | if ((hSCMan = OpenSCManager(NULL, NULL, 182 | SC_MANAGER_ALL_ACCESS)) == NULL) { 183 | return -1; 184 | } 185 | return OKAY; 186 | } 187 | 188 | void LoadDriverCleanup(void) { 189 | if (hSCMan != NULL) CloseServiceHandle(hSCMan); 190 | } 191 | 192 | /**-----------------------------------------------------**/ 193 | DWORD DriverInstall(LPSTR lpPath, LPSTR lpDriver) { 194 | BOOL dwStatus = OKAY; 195 | SC_HANDLE hService = NULL; 196 | 197 | // add to service control manager's database 198 | if ((hService = CreateService(hSCMan, lpDriver, 199 | lpDriver, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, 200 | SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpPath, 201 | NULL, NULL, NULL, NULL, NULL)) == NULL) 202 | dwStatus = GetLastError(); 203 | else CloseServiceHandle(hService); 204 | 205 | return dwStatus; 206 | } // DriverInstall 207 | 208 | /**-----------------------------------------------------**/ 209 | DWORD DriverStart(LPSTR lpDriver) { 210 | BOOL dwStatus = OKAY; 211 | SC_HANDLE hService = NULL; 212 | 213 | // get a handle to the service 214 | if ((hService = OpenService(hSCMan, lpDriver, 215 | SERVICE_ALL_ACCESS)) != NULL) 216 | { 217 | // start the driver 218 | if (!StartService(hService, 0, NULL)) 219 | dwStatus = GetLastError(); 220 | } else dwStatus = GetLastError(); 221 | 222 | if (hService != NULL) CloseServiceHandle(hService); 223 | return dwStatus; 224 | } // DriverStart 225 | 226 | /**-----------------------------------------------------**/ 227 | DWORD DriverStop(LPSTR lpDriver) 228 | { 229 | BOOL dwStatus = OKAY; 230 | SC_HANDLE hService = NULL; 231 | SERVICE_STATUS serviceStatus; 232 | 233 | // get a handle to the service 234 | if ((hService = OpenService(hSCMan, lpDriver, 235 | SERVICE_ALL_ACCESS)) != NULL) 236 | { 237 | // stop the driver 238 | if (!ControlService(hService, SERVICE_CONTROL_STOP, 239 | &serviceStatus)) 240 | dwStatus = GetLastError(); 241 | } else dwStatus = GetLastError(); 242 | 243 | if (hService != NULL) CloseServiceHandle(hService); 244 | return dwStatus; 245 | } // DriverStop 246 | 247 | /**-----------------------------------------------------**/ 248 | DWORD DriverRemove(LPSTR lpDriver) 249 | { 250 | BOOL dwStatus = OKAY; 251 | SC_HANDLE hService = NULL; 252 | 253 | // get a handle to the service 254 | if ((hService = OpenService(hSCMan, lpDriver, 255 | SERVICE_ALL_ACCESS)) != NULL) 256 | { // remove the driver 257 | if (!DeleteService(hService)) 258 | dwStatus = GetLastError(); 259 | } else dwStatus = GetLastError(); 260 | 261 | if (hService != NULL) CloseServiceHandle(hService); 262 | return dwStatus; 263 | } // DriverRemove 264 | 265 | /**-----------------------------------------------------**/ 266 | ////extensions by Lch 267 | /**-----------------------------------------------------**/ 268 | DWORD DriverStatus(LPSTR lpDriver) { 269 | BOOL dwStatus = OKAY; 270 | SC_HANDLE hService = NULL; 271 | DWORD dwBytesNeeded; 272 | 273 | // get a handle to the service 274 | if ((hService = OpenService(hSCMan, lpDriver, 275 | SERVICE_ALL_ACCESS)) != NULL) 276 | { 277 | LPQUERY_SERVICE_CONFIG lpqscBuf; 278 | //~ LPSERVICE_DESCRIPTION lpqscBuf2; 279 | // Allocate a buffer for the configuration information. 280 | if ((lpqscBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc( 281 | LPTR, 4096)) != NULL) 282 | { 283 | //~ if ((lpqscBuf2 = (LPSERVICE_DESCRIPTION) LocalAlloc( 284 | //~ LPTR, 4096)) != NULL) 285 | { 286 | // Get the configuration information. 287 | if (QueryServiceConfig( 288 | hService, 289 | lpqscBuf, 290 | 4096, 291 | &dwBytesNeeded) //&& 292 | //~ QueryServiceConfig2( 293 | //~ hService, 294 | //~ SERVICE_CONFIG_DESCRIPTION, 295 | //~ lpqscBuf2, 296 | //~ 4096, 297 | //~ &dwBytesNeeded 298 | ) 299 | { 300 | // Print the configuration information. 301 | printf("Type: [0x%02lx] ", lpqscBuf->dwServiceType); 302 | switch (lpqscBuf->dwServiceType) { 303 | case SERVICE_WIN32_OWN_PROCESS: 304 | printf("The service runs in its own process."); 305 | break; 306 | case SERVICE_WIN32_SHARE_PROCESS: 307 | printf("The service shares a process with other services."); 308 | break; 309 | case SERVICE_KERNEL_DRIVER: 310 | printf("Kernel driver."); 311 | break; 312 | case SERVICE_FILE_SYSTEM_DRIVER: 313 | printf("File system driver."); 314 | break; 315 | case SERVICE_INTERACTIVE_PROCESS: 316 | printf("The service can interact with the desktop."); 317 | break; 318 | default: 319 | printf("Unknown type."); 320 | } 321 | printf("\nStart Type: [0x%02lx] ", lpqscBuf->dwStartType); 322 | switch (lpqscBuf->dwStartType) { 323 | case SERVICE_BOOT_START: 324 | printf("Boot"); 325 | break; 326 | case SERVICE_SYSTEM_START: 327 | printf("System"); 328 | break; 329 | case SERVICE_AUTO_START: 330 | printf("Automatic"); 331 | break; 332 | case SERVICE_DEMAND_START: 333 | printf("Manual"); 334 | break; 335 | case SERVICE_DISABLED: 336 | printf("Disabled"); 337 | break; 338 | default: 339 | printf("Unknown."); 340 | } 341 | printf("\nError Control: [0x%02lx] ", lpqscBuf->dwErrorControl); 342 | switch (lpqscBuf->dwErrorControl) { 343 | case SERVICE_ERROR_IGNORE: 344 | printf("IGNORE: Ignore."); 345 | break; 346 | case SERVICE_ERROR_NORMAL: 347 | printf("NORMAL: Display a message box."); 348 | break; 349 | case SERVICE_ERROR_SEVERE: 350 | printf("SEVERE: Restart with last-known-good config."); 351 | break; 352 | case SERVICE_ERROR_CRITICAL: 353 | printf("CRITICAL: Restart w/ last-known-good config."); 354 | break; 355 | default: 356 | printf("Unknown."); 357 | } 358 | printf("\nBinary path: %s\n", lpqscBuf->lpBinaryPathName); 359 | 360 | if (lpqscBuf->lpLoadOrderGroup != NULL) 361 | printf("Load order grp: %s\n", lpqscBuf->lpLoadOrderGroup); 362 | if (lpqscBuf->dwTagId != 0) 363 | printf("Tag ID: %ld\n", lpqscBuf->dwTagId); 364 | if (lpqscBuf->lpDependencies != NULL) 365 | printf("Dependencies: %s\n", lpqscBuf->lpDependencies); 366 | if (lpqscBuf->lpServiceStartName != NULL) 367 | printf("Start Name: %s\n", lpqscBuf->lpServiceStartName); 368 | //~ if (lpqscBuf2->lpDescription != NULL) 369 | //~ printf("Description: %s\n", lpqscBuf2->lpDescription); 370 | } 371 | //~ LocalFree(lpqscBuf2); 372 | } 373 | LocalFree(lpqscBuf); 374 | } else { 375 | dwStatus = GetLastError(); 376 | } 377 | } else { 378 | dwStatus = GetLastError(); 379 | } 380 | 381 | if (hService != NULL) CloseServiceHandle(hService); 382 | return dwStatus; 383 | } // DriverStatus 384 | 385 | /**-----------------------------------------------------**/ 386 | DWORD DriverStartType(LPSTR lpDriver, DWORD dwStartType) { 387 | BOOL dwStatus = OKAY; 388 | SC_HANDLE hService = NULL; 389 | 390 | SC_LOCK sclLock; 391 | LPQUERY_SERVICE_LOCK_STATUS lpqslsBuf; 392 | DWORD dwBytesNeeded; 393 | 394 | // Need to acquire database lock before reconfiguring. 395 | sclLock = LockServiceDatabase(hSCMan); 396 | 397 | // If the database cannot be locked, report the details. 398 | if (sclLock == NULL) { 399 | // Exit if the database is not locked by another process. 400 | if (GetLastError() == ERROR_SERVICE_DATABASE_LOCKED) { 401 | 402 | // Allocate a buffer to get details about the lock. 403 | lpqslsBuf = (LPQUERY_SERVICE_LOCK_STATUS) LocalAlloc( 404 | LPTR, sizeof(QUERY_SERVICE_LOCK_STATUS)+256); 405 | if (lpqslsBuf != NULL) { 406 | // Get and print the lock status information. 407 | if (QueryServiceLockStatus( 408 | hSCMan, 409 | lpqslsBuf, 410 | sizeof(QUERY_SERVICE_LOCK_STATUS)+256, 411 | &dwBytesNeeded) ) 412 | { 413 | if (lpqslsBuf->fIsLocked) { 414 | printf("Locked by: %s, duration: %ld seconds\n", 415 | lpqslsBuf->lpLockOwner, 416 | lpqslsBuf->dwLockDuration 417 | ); 418 | } else { 419 | printf("No longer locked\n"); 420 | } 421 | } 422 | LocalFree(lpqslsBuf); 423 | } 424 | } 425 | dwStatus = GetLastError(); 426 | } else { 427 | // The database is locked, so it is safe to make changes. 428 | // Open a handle to the service. 429 | hService = OpenService( 430 | hSCMan, // SCManager database 431 | lpDriver, // name of service 432 | SERVICE_CHANGE_CONFIG 433 | ); // need CHANGE access 434 | if (hService != NULL) { 435 | // Make the changes. 436 | if (!ChangeServiceConfig( 437 | hService, // handle of service 438 | SERVICE_NO_CHANGE, // service type: no change 439 | dwStartType, // change service start type 440 | SERVICE_NO_CHANGE, // error control: no change 441 | NULL, // binary path: no change 442 | NULL, // load order group: no change 443 | NULL, // tag ID: no change 444 | NULL, // dependencies: no change 445 | NULL, // account name: no change 446 | NULL, // password: no change 447 | NULL) ) // display name: no change 448 | { 449 | dwStatus = GetLastError(); 450 | } 451 | } 452 | // Release the database lock. 453 | UnlockServiceDatabase(sclLock); 454 | } 455 | 456 | if (hService != NULL) CloseServiceHandle(hService); 457 | return dwStatus; 458 | } // DriverStartType 459 | -------------------------------------------------------------------------------- /src/win32/loaddrv_console/loaddrv.h: -------------------------------------------------------------------------------- 1 | #ifndef LOADDRV_H 2 | #define LOADDRV_H 3 | 4 | #include 5 | 6 | #define OKAY 0 7 | #define UNEXPECTED_ERROR 9999 8 | 9 | //prototypes 10 | DWORD LoadDriverInit(void); 11 | void LoadDriverCleanup(void); 12 | DWORD DriverInstall(LPSTR, LPSTR); 13 | DWORD DriverStart(LPSTR); 14 | DWORD DriverStop(LPSTR); 15 | DWORD DriverRemove(LPSTR); 16 | DWORD DriverStatus(LPSTR); 17 | DWORD DriverStartType(LPSTR, DWORD); 18 | #endif //LOADDRV_H -------------------------------------------------------------------------------- /src/win32/loaddrv_console/makefile: -------------------------------------------------------------------------------- 1 | COPT = -O2 -Wall -g -mno-cygwin 2 | 3 | loaddrv.exe: loaddrv.c loaddrv.h 4 | gcc ${COPT} $< -o $@ 5 | strip $@ 6 | -------------------------------------------------------------------------------- /src/win32/makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -mno-cygwin -mdll -O -Wall 2 | 3 | .PHONY: clean FORCE 4 | 5 | simpleio.dll: simpleio.o 6 | gcc -mno-cygwin -mdll -static -s $^ -Wl,--export-all-symbols,--kill-at -o $@ 7 | 8 | clean: 9 | rm -f simpleio.dll simpleio.o 10 | -------------------------------------------------------------------------------- /src/win32/remove_giveio.bat: -------------------------------------------------------------------------------- 1 | @set DIRVERNAME=giveio 2 | 3 | @loaddrv stop %DIRVERNAME% 4 | @if errorlevel 2 goto error 5 | 6 | @loaddrv remove %DIRVERNAME% 7 | @if errorlevel 1 goto error 8 | @goto exit 9 | 10 | :error 11 | @echo ERROR: Deinstallation of %DIRVERNAME% failed 12 | :exit 13 | @pause 14 | -------------------------------------------------------------------------------- /src/win32/simpleio.c: -------------------------------------------------------------------------------- 1 | // Parallel port extension for Win32 2 | // "inp" and "outp" are used to access the parallelport hardware 3 | // needs giveio.sys driver on NT/2k/XP 4 | // 5 | // (C) 2005 Chris Liechti 6 | // this is distributed under a free software license, see license.txt 7 | 8 | #include 9 | #include 10 | 11 | #define DRIVERNAME "\\\\.\\giveio" 12 | 13 | /* module-functions */ 14 | 15 | WINAPI void outp(int port, int value) { 16 | _outp(port, value); 17 | } 18 | 19 | WINAPI int inp(int port) { 20 | int value; 21 | value = _inp(port); 22 | return value; 23 | } 24 | 25 | WINAPI int init(void) { 26 | OSVERSIONINFO vi; 27 | 28 | //detect OS, on NT,2k,XP the driver needs to be loaded 29 | vi.dwOSVersionInfoSize = sizeof(vi); 30 | GetVersionEx(&vi); 31 | if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT) { 32 | HANDLE h; 33 | //try to open driver 34 | h = CreateFile(DRIVERNAME, GENERIC_READ, 0, NULL, 35 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 36 | if (h == INVALID_HANDLE_VALUE) { 37 | //if it fails again, then we have a problem... -> exception 38 | //"Couldn't access giveio device"; 39 | return 1; 40 | } 41 | //close again immediately. 42 | //the process is now tagged to have the rights it needs, 43 | //the giveio driver remembers that 44 | if (h != NULL) CloseHandle(h); //close the driver's file 45 | } 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/win32/simpleio.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyserial/pyparallel/c79718e4fe700286dd27cfe5f756f9b37dcf48e8/src/win32/simpleio.dll -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox is not used, but flake 8 reads this file 2 | [flake8] 3 | max-line-length = 120 4 | --------------------------------------------------------------------------------