IOWP (IO-Warrior Python)

Status: Tuesday, March 3, 2026 - 0.1.3 - Under Development! - with macOS (Apple Silicon)

Introduction

IOWJ is dead, long live IOWP!

What was IOWJ again? The goal was to wrap all IO-Warrior features into Java classes, use multiple special mode functions across several IOWs simultaneously, and handle events for pin inputs.

Additionally: Write once, run anywhere. The program shouldn't care which IOW is being used (as long as it's technically feasible).

This was also intended for operating systems. Unfortunately, macOS was left out because there was no iowkit library for it.

What is IOWP now? It is the Python port of IOWJ, but now including macOS support! For compatibility reasons, iowkit.py remains the base.

If you still want to struggle with raw reports yourself, you are welcome to do so. However, in my experience, many potential users (hobbyists) have failed because of these reports, as you have to study datasheets to apply them correctly wink.

Documentation will definitely be better (GitHub, PyPi, and the Web (this page)).

Commercial support will also be available.

Installation and Testing

Prerequisites: Python 3 installed, and git (optional, if you want to clone the repository).

Create a new working directory:

mkdir test_iowp cd test_iowp

Optional: Clone the Git repository (if you don't want to type or download examples individually).

Note: git clone only works in an empty directory!

git clone https://github.com/3zxpo/iowp.git .

I recommend using a Python virtual environment (venv).

Set up venv:

python3 -m venv .venv

Activate venv:

source ./.venv/bin/activate

Install iowp (inside the venv!):

pip install iowp

Update to a newer version of iowp:

pip install --upgrade iowp

Display the installed version of iowp:

python -c "import iowp; print(iowp.__version__)"

Show connected IOWs:

python -m iowp Number of plugged IO-Warrior device(s): 1 Device 0: IOW100 (Serial: 00000236, Rev: 0x1018)

Try Example 2 Hardware: Connect an IOW24 Dongle and a 2x16 LCD with a mounted I2C-Module (PCF8574).

Connect pins with the same names: GND, VCC, SDA, SCL.

python test_iowp_i2clcd_short.py

Note: Even if the constant mention of the venv was annoying: You only have to set it up once, but you must activate it every time you restart your computer. You can tell it’s active because the console prompt begins with "(.venv)".

Steps for Windows (User: xxx):

C:\Users\xxx\>mkdir test_iowp C:\Users\xxx\>cd test_iowp C:\Users\xxx\test_iowp>git clone https://github.com/3zxpo/iowp.git . C:\Users\xxx\test_iowp>python -m venv .venv C:\Users\xxx\test_iowp>.venv\Scripts\activate (.venv) C:\Users\xxx\test_iowp>pip install iowp (.venv) C:\Users\xxx\test_iowp>pip install --upgrade iowp (.venv) C:\Users\xxx\test_iowp>python -c "import iowp; print(iowp.__version__)" 0.1.3 (.venv) C:\Users\xxx\test_iowp>python -m iowp Number of plugged IO-Warrior device(s): Device 0: IOW24 (Serial: 00000009), Rev: 0x1029) (.venv) C:\Users\xxx\test_iowp>python test_iowp_i2clcd_short.py

Steps for Linux:

mkdir test_iowp cd test_iowp git clone https://github.com/3zxpo/iowp.git . python -m venv .venv source .venv\bin\activate pip install iowp pip install --upgrade iowp python -c "import iowp; print(iowp.__version__)" 0.1.3 sudo .venv/bin/python -m iowp Number of plugged IO-Warrior device(s): Device 0: IOW24 (Serial: 00000009), Rev: 0x1029) sudo .venv/bin/python test_iowp_i2clcd_short.py

By default, USB devices can only be used as root. To change this, the following file needs to be created:

  1. Rreate a rule file:
sudo nano /etc/udev/rules.d/99-iowarrior.rules
  1. Copy this content here: (This allows every user read and write access to IO-Warrior devices.)
SUBSYSTEM=="usb", ATTRS{idVendor}=="07c0", MODE="0666" SUBSYSTEM=="iowarrior", MODE="0666"

Examples (Requires at least iowp 0.1.2)

LCD Example 1:

Uses the LCD special mode function of any IOW (24, 28, 40, 56, and 100) and outputs simple text and a custom character.

Note: For me, the program currently only works starting from the second run!

from iowp.factory import IowFactory from iowp.lcd import LCD2x16 def main(): factory = IowFactory.get_instance() if len(factory.get_devices()) == 0: print("No IO-Warrior devices found.") return iow_dev = factory.get_iow_device(0) lcd = LCD2x16() iow_dev.add_special_mode_function_impl(lcd) print(iow_dev) try: print("Clearing LCD...") lcd.clear_lcd() print("Writing lines...") lcd.write_line(1, True, "Hello iowp!") lcd.write_line(2, True, "LCD 2x16 Ready") print("Testing special characters (smiley)...") smiley = [ 0b00000, 0b01010, 0b01010, 0b00000, 0b10001, 0b01110, 0b00000, 0b00000 ] lcd.set_special_char(0, smiley) lcd.set_cursor(1, 16) lcd.write_string(chr(0)) except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": ` main()

Terminal Output (with IOW28 connected):

IOW28:Handle[4307557680],Id[5380],Rev[0x100b],Serial[00000243],Listener[0],SpecialMode: LCD P0[IoMask[SSSSIIII],Data[XXXX1111],Listener[0]] P1[IoMask[SSSSSSSS],Data[XXXXXXXX],Listener[0]] P2[IoMask[......II],Data[......11],Listener[0]] P3[IoMask[I.......],Data[1.......],Listener[0]] Clearing LCD... Writing lines... Testing special characters (smiley)...

LCD Example 2

Uses the I2C special mode function of any IOW and the I2CLCD device (PCF8574T module for 1602 LCD) to output text and a custom character.

from iowp import IowFactory, I2C from iowp.i2c_devices import I2CLCD def main(): factory = IowFactory.get_instance() if len(factory.get_devices()) == 0: print("No IO-Warrior devices found.") return iow_dev = factory.get_iow_device(0) i2c = I2C(I2C.I2C_SPEED_100KHZ) iow_dev.add_special_mode_function_impl(i2c) lcd = I2CLCD(device_address=0x07, rows=2, cols=16) i2c.add_i2c_device(lcd) print(iow_dev) try: print("Clearing LCD...") lcd.clearLCD() print("Writing lines...") lcd.setCursor(0, 0) lcd.writeString("Hello iowp") lcd.setCursor(1, 0) lcd.writeString("LCD 2x16 Ready") print("Testing special characters (smiley)...") smiley = [ 0b00000, 0b01010, 0b01010, 0b00000, 0b10001, 0b01110, 0b00000, 0b00000 ] lcd.createChar(0, smiley) lcd.setCursor(0, 15) lcd.writeString(chr(0)) except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": main()

Terminal Output (with IOW24 connected):

IOW24:Handle[4299742512],Id[5377],Rev[0x1029],Serial[00000009],Listener[0],SpecialMode: I2C P0[IoMask[IIIIISSI],Data[11111XX1],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] Clearing LCD... Writing lines... Testing special characters (smiley)...

LCD Example 3:

Combines Example 1 and Example 2.

The methods of the two LCD objects are still different for historical reasons; this doesn't really make sense and will be changed in the future.

from iowp import IowFactory, I2C from iowp.i2c_devices import I2CLCD from iowp.lcd import LCD2x16 def main(): factory = IowFactory.get_instance() if len(factory.get_devices()) == 0: print("No IO-Warrior devices found.") return print(factory) # Iow28, SMF LCD and LCD2x16 iow_dev_28 = factory.get_iow28_device() lcd_28 = LCD2x16() iow_dev_28.add_special_mode_function_impl(lcd_28) print(iow_dev_28) # Iow24, SMF I2C and i2clcd (PCF8547 based LcD) iow_dev_24 = factory.get_iow24_device() i2c = I2C(I2C.I2C_SPEED_100KHZ) iow_dev_24.add_special_mode_function_impl(i2c) lcd_24 = I2CLCD(device_address=0x07, rows=2, cols=16) i2c.add_i2c_device(lcd_24) print(iow_dev_24) try: print("Clearing LCD...") lcd_28.clear_lcd() lcd_24.clearLCD() print("Writing lines...") lcd_28.write_line(1, True, "Hello iowp! (28)") lcd_28.write_line(2, True, "LCD 2x16 Ready") lcd_24.setCursor(0, 0) lcd_24.writeString("Hello iowp! (24)") lcd_24.setCursor(1, 0) lcd_24.writeString("LCD 2x16 Ready") print("Testing special characters (smiley)...") smiley = [ 0b00000, 0b01010, 0b01010, 0b00000, 0b10001, 0b01110, 0b00000, 0b00000 ] lcd_28.set_special_char(0, smiley) lcd_28.set_cursor(2, 16) lcd_28.write_string(chr(0)) lcd_24.createChar(0, smiley) lcd_24.setCursor(1, 15) lcd_24.writeString(chr(0)) except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": main()

Terminal Output (with IOW24 and IOW28 connected):

Number of plugged IO-Warrior device(s): 2 Device0: IOW24:Handle[4333805856],Id[5377],Rev[0x1029],Serial[00000009],Listener[0],SpecialMode: P0[IoMask[IIIIIIII],Data[11111111],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] Device1: IOW28:Handle[4333529808],Id[5380],Rev[0x100b],Serial[00000243],Listener[0],SpecialMode: P0[IoMask[IIIIIIII],Data[11111111],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] P2[IoMask[......II],Data[......11],Listener[0]] P3[IoMask[I.......],Data[1.......],Listener[0]] IOW28:Handle[4333529808],Id[5380],Rev[0x100b],Serial[00000243],Listener[0],SpecialMode: LCD P0[IoMask[SSSSIIII],Data[XXXX1111],Listener[0]] P1[IoMask[SSSSSSSS],Data[XXXXXXXX],Listener[0]] P2[IoMask[......II],Data[......11],Listener[0]] P3[IoMask[I.......],Data[1.......],Listener[0]] IOW24:Handle[4333805856],Id[5377],Rev[0x1029],Serial[00000009],Listener[0],SpecialMode: I2C P0[IoMask[IIIIISSI],Data[11111XX1],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] Clearing LCD... Writing lines... Testing special characters (smiley)...

LCD Example 4:

Example 2 twice.

from iowp import IowFactory, I2C from iowp.i2c_devices import I2CLCD def main(): factory = IowFactory.get_instance() if len(factory.get_devices()) == 0: print("No IO-Warrior devices found.") return print(factory) # Iow 1, SMF I2C and i2clcd (PCF8547 based LcD) iow_dev_1 = factory.get_iow_device(0) i2c_1 = I2C(I2C.I2C_SPEED_100KHZ) iow_dev_1.add_special_mode_function_impl(i2c_1) lcd_1 = I2CLCD(device_address=0x07, rows=2, cols=16) i2c_1.add_i2c_device(lcd_1) print(iow_dev_1) # Iow 2, SMF I2C and i2clcd (PCF8547 based LcD) iow_dev_2 = factory.get_iow_device(1) i2c_2 = I2C(I2C.I2C_SPEED_100KHZ) iow_dev_2.add_special_mode_function_impl(i2c_2) lcd_2 = I2CLCD(device_address=0x07, rows=2, cols=16) i2c_2.add_i2c_device(lcd_2) print(iow_dev_2) try: print("Clearing LCD...") lcd_1.clearLCD() lcd_2.clearLCD() print("Writing lines...") lcd_1.setCursor(0, 0) lcd_1.writeString("Hello iowp ") lcd_1.writeString(iow_dev_1.name) lcd_1.setCursor(1, 0) lcd_1.writeString("LCD 2x16 Ready") lcd_2.setCursor(0, 0) lcd_2.writeString("Hello iowp ") lcd_2.writeString(iow_dev_2.name) lcd_2.setCursor(1, 0) lcd_2.writeString("LCD 2x16 Ready") print("Testing special characters (smiley)...") smiley = [ 0b00000, 0b01010, 0b01010, 0b00000, 0b10001, 0b01110, 0b00000, 0b00000 ] lcd_1.createChar(0, smiley) lcd_1.setCursor(1, 15) lcd_1.writeString(chr(0)) lcd_2.createChar(0, smiley) lcd_2.setCursor(1, 15) lcd_2.writeString(chr(0)) except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": main()

Terminal Output (with IOW56 and IOW28 connected):

Number of plugged IO-Warrior device(s): 2 Device0: IOW56:Handle[4311368096],Id[5379],Rev[0x2001],Serial[10003856],Listener[0],SpecialMode: P0[IoMask[IIIIIIII],Data[11111111],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] P2[IoMask[IIIIIIII],Data[11111111],Listener[0]] P3[IoMask[IIIIIIII],Data[11111111],Listener[0]] P4[IoMask[IIIIIIII],Data[11111111],Listener[0]] P5[IoMask[IIIIIIII],Data[11111111],Listener[0]] P6[IoMask[IIIIIIII],Data[11111111],Listener[0]] Device1: IOW24:Handle[4308892432],Id[5377],Rev[0x1029],Serial[00000009],Listener[0],SpecialMode: P0[IoMask[IIIIIIII],Data[11111111],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] IOW56:Handle[4311368096],Id[5379],Rev[0x2001],Serial[10003856],Listener[0],SpecialMode: I2C P0[IoMask[IIIIIIII],Data[11111111],Listener[0]] P1[IoMask[SISIIIII],Data[X1X11111],Listener[0]] P2[IoMask[IIIIIIII],Data[11111111],Listener[0]] P3[IoMask[IIIIIIII],Data[11111111],Listener[0]] P4[IoMask[IIIIIIII],Data[11111111],Listener[0]] P5[IoMask[IIIIIIII],Data[11111111],Listener[0]] P6[IoMask[IIIIIIII],Data[11111111],Listener[0]] IOW24:Handle[4308892432],Id[5377],Rev[0x1029],Serial[00000009],Listener[0],SpecialMode: I2C P0[IoMask[IIIIISSI],Data[11111XX1],Listener[0]] P1[IoMask[IIIIIIII],Data[11111111],Listener[0]] Clearing LCD... Writing lines... Testing special characters (smiley)...

Explanation of Port Status (P0 to P6): You can see the function of the bits and the current logic level:

IoMask (Data pin direction):

Data (Pin status):


LCD Example 5:

Same as Example 4, but outputs occur simultaneously rather than sequentially. Here, the performance advantage of an IOW56 over an IOW24 becomes clearly visible.

import threading from iowp import IowFactory, I2C from iowp.i2c_devices import I2CLCD # Die gekapselte Methode für die LCD-Ausgabe def update_lcd(lcd, device_name): try: print(f"Starting update for {device_name}...") # 1. LCD löschen lcd.clearLCD() # 2. Text schreiben lcd.setCursor(0, 0) lcd.writeString(f"Hello iowp ") lcd.writeString(device_name) lcd.setCursor(1, 0) lcd.writeString("LCD 2x16 Ready") # 3. Sonderzeichen (Smiley) erstellen und schreiben smiley = [ 0b00000, 0b01010, 0b01010, 0b00000, 0b10001, 0b01110, 0b00000, 0b00000 ] lcd.createChar(0, smiley) lcd.setCursor(1, 15) lcd.writeString(chr(0)) print(f"Finished update for {device_name}.") except Exception as e: print(f"Error on {device_name}: {e}") def main(): factory = IowFactory.get_instance() if len(factory.get_devices()) < 2: print("Not enough IO-Warrior devices found. Need at least 2.") return # --- Setup Device 1 --- iow_dev_1 = factory.get_iow_device(0) i2c_1 = I2C(I2C.I2C_SPEED_100KHZ) iow_dev_1.add_special_mode_function_impl(i2c_1) lcd_1 = I2CLCD(device_address=0x07, rows=2, cols=16) i2c_1.add_i2c_device(lcd_1) # --- Setup Device 2 --- iow_dev_2 = factory.get_iow_device(1) i2c_2 = I2C(I2C.I2C_SPEED_100KHZ) iow_dev_2.add_special_mode_function_impl(i2c_2) lcd_2 = I2CLCD(device_address=0x07, rows=2, cols=16) i2c_2.add_i2c_device(lcd_2) # --- Threads erstellen --- # Wir übergeben die LCD-Instanz und den Namen als Argumente (args) thread1 = threading.Thread(target=update_lcd, args=(lcd_1, iow_dev_1.name)) thread2 = threading.Thread(target=update_lcd, args=(lcd_2, iow_dev_2.name)) # Threads starten (laufen jetzt parallel) thread1.start() thread2.start() # Warten, bis beide fertig sind thread1.join() thread2.join() print("Both LCDs updated simultaneously.") if __name__ == "__main__": main()

Terminal Output (with IOW28 and IOW56 connected):

Starting update for IOW56... Starting update for IOW24... Finished update for IOW56. Finished update for IOW24. Both LCDs updated simultaneously.

I would like to thank Code Mercenaries Hard- und Software GmbH for their long-term and free provision of
chips, modules, starter kits, and dongles for various IO-Warrior types. Without them, the development of
IOWJ and IOWP would not have been possible. Venceremos!

IOWP is still under development!

© 2026 by Thomas Wagner, Email: thomas@wagner-ibw.de (Updated: 2026-03-03)