Skip to content

Instantly share code, notes, and snippets.

@ybart
Last active April 26, 2026 00:52
Show Gist options
  • Select an option

  • Save ybart/8be271ae6b61997fefbd03a1f4221573 to your computer and use it in GitHub Desktop.

Select an option

Save ybart/8be271ae6b61997fefbd03a1f4221573 to your computer and use it in GitHub Desktop.
HP WiFi config over USB - no HPLIP required, adaptable to other HP LaserJet/OfficeJet/Photosmart models.
#!/usr/bin/env python3
"""
HP Printer EWS USB Proxy
========================
Exposes the HP printer's Embedded Web Server (EWS) through a local HTTP proxy,
forwarding requests over USB bulk endpoints.
Useful when the printer is not yet on WiFi and its web UI is unreachable
over the network.
Requirements:
pip install pyusb (or: sudo apt install python3-usb)
Usage:
python3 hp_ews_proxy.py
Then open: http://localhost:8080
Custom port:
python3 hp_ews_proxy.py --port 9090
Adapting for other HP printer models:
Find your printer's USB product ID with lsusb (Linux) or
system_profiler SPUSBDataType (macOS), then update HP_PRODUCT_ID below.
Also verify EWS_INTERFACE, ENDPOINT_OUT, ENDPOINT_IN match your printer
(see hp-wifi-config.py --help-adapt for full instructions).
"""
import argparse
import os
import sys
import time
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
try:
import usb.core
import usb.util
except ImportError:
print("Error: pyusb not installed.")
print("Install with: pip3 install pyusb or sudo apt install python3-usb")
sys.exit(1)
HP_VENDOR_ID = 0x03f0
HP_PRODUCT_ID = 0x102a
EWS_INTERFACE = 1
ENDPOINT_OUT = 0x02
ENDPOINT_IN = 0x82
USB_TIMEOUT_MS = 10000
usb_dev = None
usb_lock = threading.Lock()
product_id_g = HP_PRODUCT_ID
def connect_printer(product_id):
"""Find and claim the printer EWS interface. Returns device or None."""
dev = usb.core.find(idVendor=HP_VENDOR_ID, idProduct=product_id)
if dev is None:
return None
try:
if dev.is_kernel_driver_active(EWS_INTERFACE):
dev.detach_kernel_driver(EWS_INTERFACE)
usb.util.claim_interface(dev, EWS_INTERFACE)
print(f"Connected: {usb.util.get_string(dev, dev.iProduct)}")
return dev
except usb.core.USBError:
return None
def get_device():
"""Return live device or None if disconnected. Never blocks."""
global usb_dev
if usb_dev is not None:
try:
usb_dev.is_kernel_driver_active(EWS_INTERFACE)
except usb.core.USBError:
print("Printer disconnected.")
usb_dev = None
if usb_dev is None:
usb_dev = connect_printer(product_id_g)
return usb_dev
def reconnect_loop():
"""Background thread that keeps trying to reconnect."""
global usb_dev
while True:
time.sleep(1)
with usb_lock:
if usb_dev is None:
dev = connect_printer(product_id_g)
if dev:
usb_dev = dev
class EWSProxyHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
print(f" {self.command} {self.path} -> {format % args}")
def send_503(self):
crlf = b"\r\n"
msg = b"Printer disconnected - replug USB and retry"
resp = (b"HTTP/1.1 503 Service Unavailable" + crlf +
b"Content-Type: text/plain" + crlf +
b"Connection: close" + crlf +
b"Content-Length: " + str(len(msg)).encode() + crlf +
crlf + msg)
try:
self.wfile.write(resp)
self.wfile.flush()
except Exception:
pass
def do_request(self):
global usb_dev
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length) if content_length else b""
raw = (
f"{self.command} {self.path} HTTP/1.1\r\n"
f"Host: localhost\r\n"
f"Content-Length: {len(body)}\r\n"
f"Connection: close\r\n\r\n"
).encode() + body
with usb_lock:
dev = get_device()
if dev is None:
self.send_503()
return
try:
dev.write(ENDPOINT_OUT, raw, timeout=USB_TIMEOUT_MS)
response = b""
try:
while True:
chunk = bytes(dev.read(ENDPOINT_IN, 65536, timeout=USB_TIMEOUT_MS))
response += chunk
if len(chunk) < 65536:
break
except usb.core.USBTimeoutError:
pass
self.wfile.write(response)
except usb.core.USBError as e:
print(f"USB error: {e}")
usb_dev = None
self.send_503()
do_GET = do_POST = do_PUT = do_DELETE = do_HEAD = do_OPTIONS = do_request
def handle_error(self):
pass # Suppress stack trace on broken browser connections
def main():
parser = argparse.ArgumentParser(description="Proxy HP printer EWS over USB")
parser.add_argument("--port", type=int, default=8080)
args = parser.parse_args()
global usb_dev, product_id_g
product_id_g = HP_PRODUCT_ID
# Try to connect but don't require it — background thread will connect when ready
usb_dev = connect_printer(product_id_g)
if usb_dev is None:
print("Printer not found — will connect when available.")
t = threading.Thread(target=reconnect_loop, daemon=True)
t.start()
print(f"Proxy running at http://localhost:{args.port}")
print("Press Ctrl+C to stop.\n")
class QuietHTTPServer(HTTPServer):
def handle_error(self, request, client_address):
pass # Suppress stack traces on broken connections
try:
QuietHTTPServer(("127.0.0.1", args.port), EWSProxyHandler).serve_forever()
except KeyboardInterrupt:
print("\nStopping.")
finally:
try:
usb.util.release_interface(usb_dev, EWS_INTERFACE)
usb_dev.attach_kernel_driver(EWS_INTERFACE)
except Exception:
pass
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
HP LaserJet P1102w WiFi Configuration Tool
==========================================
Pushes WiFi credentials to the printer over USB using the HP EWS/LEDM interface.
No HPLIP, no CUPS, no hp-wificonfig required.
Requirements:
pip install pyusb (or: sudo apt install python3-usb)
Usage:
# Auto-detect current WiFi and push to printer (Linux/macOS):
python3 hp_wifi_config.py --auto
# Manually specify credentials:
python3 hp_wifi_config.py --ssid "MyNetwork" --password "MyPassword"
python3 hp_wifi_config.py --ssid "MyNetwork" --password "MyPassword" --security WPA_PSK
# Show printer WiFi status:
python3 hp_wifi_config.py --status
Adapting for other HP printer models:
This script can be adapted for most HP LaserJet, OfficeJet, and Photosmart
printers from ~2009 onwards. See the "Adapting for another HP printer model"
section below for step-by-step instructions.
Supported security types:
WPA_PSK (default), WPA2_PSK, WEP, none
Auto-detect notes:
Linux : reads SSID + password from NetworkManager via nmcli (requires sudo)
macOS : reads SSID via ipconfig setverbose, password from Keychain (requires sudo)
The script will automatically re-exec itself with sudo if needed.
Adapting for another HP printer model:
This script uses the HP EWS/LEDM protocol over USB, which is supported by most
HP LaserJet, OfficeJet, and Photosmart printers from ~2009 onwards.
1. Find your printer's USB IDs:
lsusb (Linux)
system_profiler SPUSBDataType (macOS)
Look for "Hewlett-Packard" or "HP" and note idVendor and idProduct.
Example: "ID 03f0:102a" -> HP_VENDOR_ID=0x03f0, HP_PRODUCT_ID=0x102a
2. Update the constants near the top of this script:
HP_VENDOR_ID = 0x03f0 # always 0x03f0 for HP
HP_PRODUCT_ID = 0x102a # change this to your printer's product ID
3. Find the EWS interface number and endpoints:
sudo python3 -c "
import usb.core, usb.util
dev = usb.core.find(idVendor=0x03f0, idProduct=0xYOUR_ID)
print(dev)
"
Look for an interface with:
bInterfaceClass : 0xff (Vendor Specific)
bInterfaceSubClass : 0x02
bInterfaceProtocol : 0x10
iInterface : HP EWS (or similar)
Note its interface number and the Bulk OUT / Bulk IN endpoint addresses.
Update EWS_INTERFACE, ENDPOINT_OUT, ENDPOINT_IN accordingly.
4. Verify the EWS interface works:
sudo python3 -c "
import usb.core, usb.util
dev = usb.core.find(idVendor=0x03f0, idProduct=0xYOUR_ID)
if dev.is_kernel_driver_active(EWS_INTERFACE):
dev.detach_kernel_driver(EWS_INTERFACE)
usb.util.claim_interface(dev, EWS_INTERFACE)
req = b'GET /IoMgmt/Adapters HTTP/1.1
Host: localhost
Content-Length: 0
Connection: close
'
dev.write(ENDPOINT_OUT, req, timeout=10000)
print(bytes(dev.read(ENDPOINT_IN, 4096, timeout=10000)).decode())
"
If you get a 200 OK with XML, the interface works and the rest of the
script should work as-is.
5. If the WiFi adapter is not named "wifi0", check the ResourceURI values
in the GET /IoMgmt/Adapters response and update the endpoint in set_wifi():
endpoint = "/IoMgmt/Adapters/wifi0/Profiles/Active"
"""
import argparse
import binascii
import os
import subprocess
import sys
import re
try:
import usb.core
import usb.util
except ImportError:
print("Error: pyusb not installed.")
if sys.platform == "darwin":
print("Install with: pip3 install pyusb")
print(" or: python3 -m pip install pyusb")
else:
print("Install with: sudo apt install python3-usb")
print(" or: pip3 install pyusb")
sys.exit(1)
# HP LaserJet P1102w USB identifiers
HP_VENDOR_ID = 0x03f0
HP_PRODUCT_ID = 0x102a
# USB interface and endpoints
EWS_INTERFACE = 1
ENDPOINT_OUT = 0x02 # Bulk OUT
ENDPOINT_IN = 0x82 # Bulk IN
USB_TIMEOUT_MS = 10000
# ─────────────────────────────────────────────
# WiFi credential auto-detection
# ─────────────────────────────────────────────
def get_wifi_linux():
"""
Read current WiFi SSID and password from NetworkManager on Linux.
Requires sudo (nmcli needs elevated privileges to show passwords).
"""
# Get current connected SSID
try:
result = subprocess.run(
["nmcli", "-t", "-f", "active,ssid", "dev", "wifi"],
capture_output=True, text=True, check=True
)
ssid = None
for line in result.stdout.splitlines():
if line.startswith("yes:"):
ssid = line.split(":", 1)[1].strip()
break
if not ssid:
return None, None, None
except (subprocess.CalledProcessError, FileNotFoundError):
return None, None, None
# Get password and security type for this SSID
try:
result = subprocess.run(
["nmcli", "-s", "-g",
"802-11-wireless-security.key-mgmt,"
"802-11-wireless-security.psk,"
"802-11-wireless-security.wep-key0",
"connection", "show", ssid],
capture_output=True, text=True, check=True
)
lines = result.stdout.strip().splitlines()
key_mgmt = lines[0].strip() if len(lines) > 0 else ""
password = lines[1].strip() if len(lines) > 1 else ""
wep_key = lines[2].strip() if len(lines) > 2 else ""
if not password and wep_key:
password = wep_key
# Map nmcli key-mgmt to printer security type
security_map = {
"wpa-psk": "WPA_PSK",
"wpa-eap": "WPA_PSK",
"none": "none",
"": "none",
}
security = security_map.get(key_mgmt.lower(), "WPA_PSK")
return ssid, password, security
except (subprocess.CalledProcessError, FileNotFoundError):
return ssid, None, None
def get_wifi_macos():
"""
Read current WiFi SSID and password on macOS.
Uses ipconfig setverbose 1 to unredact SSID (requires sudo).
Password is read from Keychain.
"""
ssid = None
# Enable verbose mode to unredact SSID, then restore it
try:
subprocess.run(["ipconfig", "setverbose", "1"],
capture_output=True, check=True)
result = subprocess.run(["ifconfig", "-l"],
capture_output=True, text=True)
interfaces = [i for i in result.stdout.split() if i.startswith("en")]
for iface in interfaces:
r = subprocess.run(["ipconfig", "getsummary", iface],
capture_output=True, text=True)
for line in r.stdout.splitlines():
if re.match(r'^\s*SSID\s*:', line):
ssid = line.split(":", 1)[1].strip()
break
if ssid:
break
except (subprocess.CalledProcessError, FileNotFoundError):
pass
finally:
subprocess.run(["ipconfig", "setverbose", "0"],
capture_output=True)
if not ssid:
return None, None, None
# Get password from Keychain
password = None
for cmd in [
["security", "find-generic-password",
"-D", "AirPort network password", "-a", ssid, "-w"],
["security", "find-generic-password", "-s", ssid, "-w"],
]:
try:
result = subprocess.run(cmd, capture_output=True,
text=True, check=True)
password = result.stdout.strip()
if password:
break
except subprocess.CalledProcessError:
continue
return ssid, password, "WPA_PSK"
def auto_detect_wifi():
"""Detect current WiFi credentials from the OS."""
if sys.platform.startswith("linux"):
print("Detecting WiFi credentials from NetworkManager...")
ssid, password, security = get_wifi_linux()
elif sys.platform == "darwin":
print("Detecting WiFi credentials from macOS Keychain...")
ssid, password, security = get_wifi_macos()
else:
print(f"Auto-detect not supported on platform: {sys.platform}")
print("Use --ssid and --password instead.")
sys.exit(1)
if not ssid:
print("Error: could not detect current WiFi network.")
print("Make sure you are connected to WiFi, or use --ssid/--password.")
sys.exit(1)
if not password:
print(f"Detected SSID: {ssid}")
print("Warning: could not retrieve password automatically.")
import getpass
password = getpass.getpass(f"Enter WiFi password for '{ssid}': ")
return ssid, password, security or "WPA_PSK"
# ─────────────────────────────────────────────
# USB / printer communication
# ─────────────────────────────────────────────
def find_printer():
dev = usb.core.find(idVendor=HP_VENDOR_ID, idProduct=HP_PRODUCT_ID)
if dev is None:
print("Error: HP LaserJet P1102w not found.")
print("Make sure the printer is connected via USB and powered on.")
sys.exit(1)
return dev
def claim_ews_interface(dev):
try:
if dev.is_kernel_driver_active(EWS_INTERFACE):
dev.detach_kernel_driver(EWS_INTERFACE)
print(f"Detached kernel driver from interface {EWS_INTERFACE}")
usb.util.claim_interface(dev, EWS_INTERFACE)
print(f"Claimed EWS interface (interface {EWS_INTERFACE})")
except usb.core.USBError as e:
print(f"Error claiming EWS interface: {e}")
print("Try running with sudo.")
sys.exit(1)
def release_ews_interface(dev):
try:
usb.util.release_interface(dev, EWS_INTERFACE)
dev.attach_kernel_driver(EWS_INTERFACE)
except Exception:
pass
def send_http_request(dev, method, path, body=None, content_type="text/xml; charset=utf-8"):
body_bytes = body.encode("utf-8") if isinstance(body, str) else (body or b"")
request = (
f"{method} {path} HTTP/1.1\r\n"
f"Host: localhost\r\n"
f"User-Agent: hp-wifi-config/1.0\r\n"
f"Content-Type: {content_type}\r\n"
f"Content-Length: {len(body_bytes)}\r\n"
f"Connection: close\r\n"
f"\r\n"
).encode("utf-8") + body_bytes
dev.write(ENDPOINT_OUT, request, timeout=USB_TIMEOUT_MS)
response = b""
try:
while True:
chunk = bytes(dev.read(ENDPOINT_IN, 4096, timeout=USB_TIMEOUT_MS))
response += chunk
if len(chunk) < 4096:
break
except usb.core.USBTimeoutError:
pass
text = response.decode("utf-8", errors="replace")
return decode_chunked(text)
def decode_chunked(response):
"""Strip HTTP chunked transfer encoding from response body."""
sep = chr(13) + chr(10) + chr(13) + chr(10)
crlf = chr(13) + chr(10)
if sep not in response:
return response
headers, body = response.split(sep, 1)
if "chunked" not in headers.lower():
return response
result = []
while body:
pos = body.find(crlf)
if pos == -1:
break
size_str = body[:pos].split(";")[0].strip()
try:
size = int(size_str, 16)
except ValueError:
break
if size == 0:
break
chunk = body[pos + 2: pos + 2 + size]
result.append(chunk)
body = body[pos + 2 + size + 2:]
return headers + sep + "".join(result)
def decode_ssid(hex_ssid):
try:
return binascii.unhexlify(hex_ssid.strip()).decode("utf-8")
except Exception:
return hex_ssid
def encode_ssid(ssid):
return ssid.encode("utf-8").hex().upper()
def parse_adapters(response):
adapters = []
for block in re.findall(r'<io:Adapter>(.*?)</io:Adapter>', response, re.DOTALL):
adapter = {}
for key, pattern in [
("uri", r'<dd:ResourceURI>(.*?)</dd:ResourceURI>'),
("name", r'<dd:Name>(.*?)</dd:Name>'),
("power", r'<dd:Power>\s*(.*?)\s*</dd:Power>'),
("security", r'<wifi:EncryptionType>\s*(.*?)\s*</wifi:EncryptionType>'),
("connected", r'<dd:IsConnected>\s*(.*?)\s*</dd:IsConnected>'),
("status", r'<dd:NetworkStatus>\s*(.*?)\s*</dd:NetworkStatus>'),
]:
m = re.search(pattern, block, re.DOTALL)
if m:
adapter[key] = m.group(1).strip()
m = re.search(r'<wifi:SSID>\s*([0-9A-Fa-f\s]+?)\s*</wifi:SSID>', block, re.DOTALL)
if m:
raw = re.sub(r'\s+', '', m.group(1))
adapter["ssid_hex"] = raw
adapter["ssid"] = decode_ssid(raw)
adapters.append(adapter)
return adapters
def get_status(dev):
print("\nQuerying printer WiFi status...")
response = send_http_request(dev, "GET", "/IoMgmt/Adapters")
if "200 OK" not in response:
print("Error: unexpected response:")
print(response[:500])
return
adapters = parse_adapters(response)
if not adapters:
print("No adapters found.")
return
print(f"\nFound {len(adapters)} WiFi adapter(s):\n")
for a in adapters:
print(f" Adapter : {a.get('name', 'unknown')}")
print(f" URI : {a.get('uri', 'unknown')}")
print(f" Power : {a.get('power', 'unknown')}")
if "ssid" in a:
print(f" SSID : {a['ssid']}")
if "security" in a:
print(f" Security : {a['security']}")
if "connected" in a:
print(f" Connected: {a['connected']}")
if "status" in a:
print(f" Status : {a['status']}")
print()
# ─────────────────────────────────────────────
# WiFi configuration
# ─────────────────────────────────────────────
def build_wifi_xml(ssid, password, security, communication_mode="infrastructure"):
"""
Build the XML payload for WiFi association PUT request.
Based on HPLIP LedmWifi.py associate() function.
Endpoint: /IoMgmt/Adapters/wifi0/Profiles/Active
"""
ssid_hex = encode_ssid(ssid)
password_hex = password.encode("utf-8").hex()
ns = (
' xmlns:io="http://www.hp.com/schemas/imaging/con/ledm/iomgmt/2008/11/30"'
' xmlns:dd="http://www.hp.com/schemas/imaging/con/dictionaries/1.0/"'
' xmlns:wifi="http://www.hp.com/schemas/imaging/con/wifi/2009/06/26"'
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
' xsi:schemaLocation="http://www.hp.com/schemas/imaging/con/ledm/iomgmt/2008/11/30'
' ../../schemas/IoMgmt.xsd'
' http://www.hp.com/schemas/imaging/con/dictionaries/1.0/'
' ../../schemas/dd/DataDictionaryMasterLEDM.xsd"'
)
if security == "none":
return (
f'<?xml version="1.0" encoding="UTF-8"?>'
f'<io:Profile{ns}>'
f'<io:AdapterProfile><io:WifiProfile>'
f'<wifi:SSID>{ssid_hex}</wifi:SSID>'
f'<wifi:CommunicationMode>{communication_mode}</wifi:CommunicationMode>'
f'<wifi:EncryptionType>{security}</wifi:EncryptionType>'
f'<wifi:AuthenticationMode>open</wifi:AuthenticationMode>'
f'</io:WifiProfile></io:AdapterProfile></io:Profile>'
)
else:
return (
f'<?xml version="1.0" encoding="UTF-8"?>'
f'<io:Profile{ns}>'
f'<io:AdapterProfile><io:WifiProfile>'
f'<wifi:SSID>{ssid_hex}</wifi:SSID>'
f'<wifi:CommunicationMode>{communication_mode}</wifi:CommunicationMode>'
f'<wifi:EncryptionType>{security}</wifi:EncryptionType>'
f'<wifi:AuthenticationMode>{security}</wifi:AuthenticationMode>'
f'<io:KeyInfo><io:WpaPassPhraseInfo>'
f'<wifi:RsnEncryption>AESOrTKIP</wifi:RsnEncryption>'
f'<wifi:RsnAuthorization>autoWPA</wifi:RsnAuthorization>'
f'<wifi:PassPhrase>{password_hex}</wifi:PassPhrase>'
f'</io:WpaPassPhraseInfo></io:KeyInfo>'
f'</io:WifiProfile></io:AdapterProfile></io:Profile>'
)
def set_wifi(dev, ssid, password, security):
print(f"\nConfiguring WiFi:")
print(f" SSID : {ssid}")
print(f" Security: {security}")
print(f" Password: {'*' * len(password)}")
print("\nVerifying printer is reachable...")
response = send_http_request(dev, "GET", "/IoMgmt/Adapters")
if "200 OK" not in response:
print("Error: could not reach printer EWS interface.")
sys.exit(1)
print("Printer EWS interface OK.")
endpoint = "/IoMgmt/Adapters/wifi0/Profiles/Active"
xml_body = build_wifi_xml(ssid, password, security)
print(f"\nSending WiFi configuration to {endpoint}...")
response = send_http_request(dev, "PUT", endpoint, body=xml_body)
first_line = response.split("\r\n")[0] if "\r\n" in response else response[:100]
print(f"Printer response: {first_line}")
if any(code in response for code in ["200 OK", "204", "202"]):
print("\n✓ WiFi configuration sent successfully!")
print(" The printer will now attempt to connect to the new network.")
print(" This may take 30-60 seconds.")
print(" Print a config page (hold Go ~5 sec) to verify the new IP.")
elif "400" in response:
print("\n✗ Bad request — check security type.")
print(" Try: --security WPA2_PSK or --security WPA_PSK")
print("\nFull response:\n" + response[:1000])
elif "401" in response or "403" in response:
print("\n✗ Authentication error — printer may have a password set.")
elif "404" in response:
print("\n✗ Endpoint not found.\nFull response:\n" + response[:500])
else:
print(f"\n? Unexpected response:\n" + response[:1000])
# ─────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────
def main():
# Re-exec with sudo if not root
if os.geteuid() != 0:
print("Requesting sudo privileges...")
os.execvp("sudo", ["sudo", sys.executable] + sys.argv)
parser = argparse.ArgumentParser(
description="Configure HP LaserJet P1102w WiFi over USB",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 hp_wifi_config.py --auto
python3 hp_wifi_config.py --status
python3 hp_wifi_config.py --ssid "MyNetwork" --password "MyPassword"
python3 hp_wifi_config.py --ssid "MyNetwork" --password "MyPassword" --security WPA2_PSK
Adapting for other HP models:
This script supports most HP LaserJet, OfficeJet, and Photosmart printers
from ~2009 onwards. Run: python3 hp_wifi_config.py --help-adapt
"""
)
parser.add_argument("--help-adapt", action="store_true",
help="Show instructions for adapting this script to other HP printer models")
parser.add_argument("--auto", action="store_true",
help="Auto-detect current WiFi credentials from OS and push to printer")
parser.add_argument("--status", action="store_true",
help="Show current printer WiFi status")
parser.add_argument("--ssid", help="WiFi network name (SSID)")
parser.add_argument("--password", help="WiFi password")
parser.add_argument("--security", default="WPA_PSK",
choices=["WPA_PSK", "WPA2_PSK", "WEP", "none"],
help="Security type (default: WPA_PSK)")
args = parser.parse_args()
if args.help_adapt:
import re as _re
docstring = __doc__
m = _re.search(r'(Adapting for another HP printer model:.*)', docstring, _re.DOTALL)
if m:
print(m.group(1))
sys.exit(0)
if not args.status and not args.auto and not (args.ssid and args.password):
parser.print_help()
print("\nError: provide --status, --auto, or both --ssid and --password.")
sys.exit(1)
print("HP LaserJet P1102w WiFi Configuration Tool")
print("=" * 45)
ssid = password = security = None
if args.auto:
ssid, password, security = auto_detect_wifi()
print(f"\nDetected SSID : {ssid}")
print(f"Detected security: {security}")
print(f"Password : {'*' * len(password)}")
confirm = input("\nPush these credentials to the printer? [y/N] ").strip().lower()
if confirm != "y":
print("Aborted.")
sys.exit(0)
elif args.ssid:
ssid = args.ssid
password = args.password
security = args.security
dev = find_printer()
print(f"\nFound printer: {usb.util.get_string(dev, dev.iProduct)}")
print(f"Serial number: {usb.util.get_string(dev, dev.iSerialNumber)}")
claim_ews_interface(dev)
try:
if args.status:
get_status(dev)
else:
set_wifi(dev, ssid, password, security)
finally:
release_ews_interface(dev)
if __name__ == "__main__":
main()
@ybart
Copy link
Copy Markdown
Author

ybart commented Apr 25, 2026

This script was born out of frustration with HP's WiFi setup tooling on Linux and macOS:

Why is this so hard?

With only HP's own tools, WPS is the only realistic option on modern macOS:

  • HP Easy Start could not be downloaded from HP's website
  • HP Smart (the current HP app) is mostly a marketing tool for HP services. It only sees already-networked printers, doesn't work over USB, and tries to connect via HTTPS, which this printer doesn't support. No initial WiFi setup feature at all.
  • HP Utility / Setup Assistant did support USB-based WiFi setup on macOS but was discontinued long ago. It only works on old macOS versions, if you can even find it.
  • Wireless Direct (the printer's own hotspot) requires being enabled first, which requires accessing the EWS, which requires the printer to already be on the network. Chicken and egg.
  • HPLIP's hp-wificonfig on Linux hangs silently due to a bug: openEWS_LEDM() failure is never checked, causing an infinite block on the first HTTP read. The underlying cause of openEWS_LEDM() returning HPMUD_R_INVALID_STATE was not fully identified.

So unless your router has WPS, you're stuck, unless you use this proxy to access the EWS (embedded web server) over USB, enable Wireless Direct, then configure WiFi from your browser.

This script talks directly to the printer's HP EWS/LEDM HTTP interface over USB bulk endpoints using pyusb.

Tested on HP LaserJet P1102w, Ubuntu 26.04 (VM) and macOS Tahoe. Should work on most HP LaserJet/OfficeJet/Photosmart from ~2009 onwards — see --help-adapt for instructions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment