In this post, we’ll walk through the process of connecting a thermal printer or any external devices like a barcode reader, passport scanner to a Windows OS using Django. I am using the Oscar POS purchased from Amazon. We’ll cover setting up a virtual environment, creating a Django project, and building an interface for dynamic printing using win32print
.
The project code can be found here: GitHub Repository
Before starting, you should install the drivers that come with this printer. Other models do not require any driver to build a program to use them by using the pyusb
library. More expensive devices have product_id
and vendor_id
.
At the end, I will provide a brief detail about using the pyusb
library and connecting devices to it.
Prerequisites
Before we begin, ensure you have the following:
- Python installed on your system
- Django installed in your Python environment
- A POS thermal printer for Invoices and Queue Tickets with the necessary drivers installed on your Windows OS
Step 1: Setting Up the Virtual Environment
First, we’ll create a virtual environment for our project. Open your command prompt and run the following commands:
# Install virtualenv if you haven't already
pip install virtualenv
# Create a virtual environment named 'printer_env'
virtualenv printer_env
# Activate the virtual environment
# On Windows
printer_env\Scripts\activate
# On macOS/Linux
source printer_env/bin/activate
Step 2: Installing Required Packages
Within the virtual environment, install Django and other required packages:
pip install django pywin32
Step 3: Setting Up the Django Project
Create a new Django project and app:
# Create a Django project named 'printer_project'
django-admin startproject printer_project
# Navigate to the project directory
cd printer_project
# Create a Django app named 'pos'
python manage.py startapp pos
Add the new app to your INSTALLED_APPS
in printer_project/settings.py
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pos',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / "templates"], # Add this line
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Step 4: Setting Up Models
Define the models for saving sequence numbers and printer preferences. In pos/models.py
, add:
from django.db import models
class Sequence(models.Model):
number = models.IntegerField(default=0)
class PrinterPreference(models.Model):
device_name = models.CharField(max_length=255)
device_id = models.CharField(max_length=255, unique=True)
is_default = models.BooleanField(default=False)
def __str__(self):
return f"{self.device_name} - Default: {self.is_default}"
Run the migrations to create these models in the database:
python manage.py makemigrations pos
python manage.py migrate
Step 5: Setting Up the Admin Interface
Register your models in pos/admin.py
:
from django.contrib import admin
from .models import PrinterPreference, Sequence
admin.site.register(PrinterPreference)
admin.site.register(Sequence)
Step 6: Creating Views and Templates
Create views for handling sequence numbers and listing USB devices in pos/views.py
:
from django.http import JsonResponse, HttpResponse
from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from .models import Sequence, PrinterPreference
import win32print
import win32com.client
import pythoncom
import logging
def home(request):
return render(request, "sequence.html")
@csrf_exempt # For demonstration purposes only, consider CSRF in production
@require_http_methods(["POST"])
def sequence_view(request):
number_to_print = request.POST.get('number_to_print')
if 'reset' in request.POST:
Sequence.objects.update(number=0)
return JsonResponse({'number': 0, 'status': 'Sequence reset'})
sequence = Sequence.objects.get(id=1)
if number_to_print:
try:
number_to_print = int(number_to_print)
sequence.number = number_to_print
sequence.save()
response = print_sequence(sequence.number)
return JsonResponse({'number': number_to_print, 'status': 'Sequence Setting Success', **response})
except ValueError:
return JsonResponse({'status': 'Invalid number'}, status=400)
else:
sequence.number += 1
sequence.save()
response = print_sequence(sequence.number)
return JsonResponse({'number': sequence.number, **response})
def print_sequence(number):
data = b'\x1b\x40'
data += b'\x1b\x37\x06\x15\x10'
data += b'\x1b\x21\x30'
data += 'Your Company Name\n'.encode('utf-8')
data += b'\x1b\x21\x00'
data += 'Registration\n'.encode('utf-8')
data += b'\x1b\x21\x10'
data += 'INFO : \n'.encode('utf-8')
data += b'--------------------------------\n'
data += b'\x1b\x21\x30'
data += 'TOKEN NO:{}\n'.format(number).encode('utf-8')
data += b'\x1d\x56\x42\x00'
data += b'\x1B\x42\x00\x00'
try:
default_printer = PrinterPreference.objects.filter(is_default=True).first()
if not default_printer:
return {"status": "error", "message": "No default printer selected."}
printer_name = default_printer.device_name
printer_handle = win32print.OpenPrinter(printer_name)
try:
job_info = ("Token Print", None, "RAW")
job_id = win32print.StartDocPrinter(printer_handle, 1, job_info)
win32print.StartPagePrinter(printer_handle)
win32print.WritePrinter(printer_handle, data)
win32print.EndPagePrinter(printer_handle)
win32print.EndDocPrinter(printer_handle)
finally:
win32print.ClosePrinter(printer_handle)
return {"status": "success", "message": f"Printed sequence number {number}"}
except Exception as e:
return {"status": "error", "message": f"An error occurred: {e}"}
logging.basicConfig(level=logging.DEBUG)
def list_devices():
try:
pythoncom.CoInitialize()
wmi = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmi_service = wmi.ConnectServer(".", "root\cimv2")
devices = wmi_service.ExecQuery("SELECT * FROM Win32_PnPEntity")
device_list = [{'device_name': device.Name} for device in devices]
return device_list
except pythoncom.com_error as e:
return [{'error': 'Failed to list devices due to a WMI error.'}]
finally:
pythoncom.CoUninitialize()
@csrf_exempt # For demonstration purposes only, consider CSRF in production
@require_http_methods(["GET", "POST"])
def list_usb_devices(request):
if request.method == "POST":
default_printer_name = request.POST.get('default_printer')
if default_printer_name:
PrinterPreference.objects.all().update(is_default=False)
our_printer, created = PrinterPreference.objects.get_or_create(device_name=default_printer_name)
if our_printer:
our_printer.is_default = True
our_printer.save()
return redirect('pos:home')
device_list = list_devices()
if not device_list:
device_list = [{'error': "No USB devices found."}]
return render(request, 'list_usb_devices.html', {'devices': device_list})
Create the templates sequence.html
and list_usb_devices.html
in pos/templates/
:
sequence.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sequence Number</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
<style>
.btn-pos { background-color: orange; color: white; padding: 20px 40px; font-size: 1.5rem; margin: 10px 0; }
.btn-pos:hover { background-color: darkorange; }
.input-pos { font-size: 1.5rem; padding: 10px
; }
.logo-corner { position: absolute; top: 10px; left: 10px; }
.container { max-width: 800px; }
</style>
<script>
$(document).ready(function() {
$('form').on('submit', function(e) {
e.preventDefault();
var numberToPrint = $('#number_to_print').val();
var reset = $('button[name="reset"]').is(':focus');
var postData = {'csrfmiddlewaretoken': '{{ csrf_token }}'};
if (reset) {
postData['reset'] = true;
} else {
postData['number_to_print'] = numberToPrint;
}
$.ajax({
type: 'POST',
url: "{% url 'pos:sequence_view' %}",
data: postData,
success: function(response) {
if (response.status === "success" || response.status === "Sequence reset") {
$('#number').text(response.number);
if (reset) {
$('form')[0].reset();
}
} else if (response.status === "error") {
alert("Error: " + response.message);
}
},
error: function(error) {
console.log(error);
alert('An error occurred.');
}
});
});
});
</script>
</head>
<body>
<div class="logo-corner">
<img src="https://eu-central-1.linodeobjects.com/roadslink/images/file.png" alt="Logo" height="60">
</div>
<div class="container mt-5">
<h1 class="mb-3">Sequence Number: <span id="number">{{ number }}</span></h1>
<form method="post" class="text-center">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control input-pos" id="number_to_print" name="number_to_print" placeholder="Enter/Set a number to print">
</div>
<button type="submit" name="print_next" class="btn btn-pos btn-block">Print Next Number</button>
<button type="submit" name="reset" class="btn btn-pos btn-block">Reset</button>
</form>
<a href="{% url 'pos:list_usb_devices' %}">Select your device if not running</a>
</div>
</body>
</html>
list_usb_devices.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>List USB Devices</title>
</head>
<body>
<h1>Select Default Printer</h1>
{% if error %}
<p style="color: red;">Error: {{ error }}</p>
{% endif %}
<form method="post">
{% csrf_token %}
<ul>
{% for device in devices %}
<li>
<label>
<input type="radio" name="default_printer" value="{{ device.device_name }}">
{{ device.device_name }}
</label>
</li>
{% endfor %}
</ul>
<button type="submit">Set as Default Printer</button>
</form>
</body>
</html>
Step 7: Setting Up URL Routes
Define URL routes for your views in pos/urls.py
:
from django.urls import path
from .views import home, list_usb_devices, sequence_view
app_name = 'pos'
urlpatterns = [
path('', home, name='home'),
path('sequence_view', sequence_view, name='sequence_view'),
path('list_usb_devices/', list_usb_devices, name='list_usb_devices'),
]
Include the pos
app URLs in the main project URL configuration in printer_project/urls.py
:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pos.urls')),
]
Step 8: Running the Project
Start the Django development server:
python manage.py runserver
Navigate to http://127.0.0.1:8000/
in your web browser. You should see the sequence number interface where you can print the next number or reset the sequence. Use the link http://127.0.0.1:8000/list_usb_devices
to select and set your default printer.
Testing with pyusb
Using this method is more product support but requires verified product factory who register his devices in all operating systems like Windows or Linux.
pip install pyusb
import usb.core
import usb.util
def list_usb_devices():
# Find all connected USB devices
devices = usb.core.find(find_all=True)
# Iterate over each device and print information
for device in devices:
print(f"Device: {device}")
print(f" - idVendor: {hex(device.idVendor)}")
print(f" - idProduct: {hex(device.idProduct)}")
print(f" - Manufacturer: {usb.util.get_string(device, device.iManufacturer)}")
print(f" - Product: {usb.util.get_string(device, device.iProduct)}")
print(f" - Serial Number: {usb.util.get_string(device, device.iSerialNumber)}")
print()
if __name__ == "__main__":
list_usb_devices()
Result:
Device: DEVICE ID 1d6b:0003 on Bus 004 Address 001
=================
bLength : 0x12 (18 bytes)
bDescriptorType : 0x1 Device
bcdUSB : 0x310 USB 3.1
bDeviceClass : 0x9 Hub
bDeviceSubClass : 0x0
bDeviceProtocol : 0x3
bMaxPacketSize0 : 0x9 (9 bytes)
idVendor : 0x1d6b
idProduct : 0x0003
bcdDevice : 0x602 Device 6.02
iManufacturer : 0x3 Error Accessing String
iProduct : 0x2 Error Accessing String
iSerialNumber : 0x1 Error Accessing String
bNumConfigurations : 0x1
CONFIGURATION 1: 0 mA
====================================
bLength : 0x9 (9 bytes)
bDescriptorType : 0x2 Configuration
wTotalLength : 0x1f (31 bytes)
bNumInterfaces : 0x1
bConfigurationValue : 0x1
iConfiguration : 0x0
bmAttributes : 0xe0 Self Powered, Remote Wakeup
bMaxPower : 0x0 (0 mA)
INTERFACE 0: Hub
=======================================
bLength : 0x9 (9 bytes)
bDescriptorType : 0x4 Interface
bInterfaceNumber : 0x0
bAlternateSetting : 0x0
bNumEndpoints : 0x1
bInterfaceClass : 0x9 Hub
bInterfaceSubClass : 0x0
bInterfaceProtocol : 0x0
iInterface : 0x0
ENDPOINT 0x81: Interrupt IN
==========================
bLength : 0x7 (7 bytes)
bDescriptorType : 0x5 Endpoint
bEndpointAddress : 0x81 IN
bmAttributes : 0x3 Interrupt
wMaxPacketSize : 0x4 (4 bytes)
bInterval : 0xc
- idVendor: 0x1d6b
- idProduct: 0x3
Part 2: I will make another article about pyusb
with a passport scanner.
Bonus Section
I have created Docker composing and a Dockerfile for building an image that will work in any Operating Systems.
Docker Installation
To run this project in a Docker container, use the following Dockerfile and docker-compose.yml
.
Dockerfile
FROM python:3.11
# Install dependencies
RUN apt-get update && apt-get install -y \
usbutils \
libusb-1.0-0-dev \
&& rm -rf /var/lib/apt/lists/*
# Set work directory
WORKDIR /app
# Install Python dependencies
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
# Copy project files
COPY . .
# Set the environment variable for Django settings
ENV DJANGO_SETTINGS_MODULE=printer.settings
# Expose port 8000
EXPOSE 8000
# Run the Django server
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
docker-compose.yml
version: '3.10'
services:
web:
build: .
command: sh -c "python manage.py runserver 0.0.0.0:8000"
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DJANGO_SETTINGS_MODULE=printer.settings
devices:
- "/dev/bus/usb:/dev/bus/usb"
privileged: true
Conclusion
In this post, we covered setting up a virtual environment, creating a Django project, and building an interface for dynamic printing using win32print
. By following these steps, you can connect and use a thermal printer with a Django web application on Windows OS. This setup allows for flexible and dynamic printing solutions tailored to your specific needs.
Feel free to customize the code and templates to suit your project requirements. If you have any questions or run into issues, leave a comment below, and I’ll be happy to help.
Happy printing and device integration!!
Top comments (0)