We built Fakebox because we wanted to reproduce PosBox related issues without having to actually run a PosBox. It is a local HTTP server that speaks the same hw_proxy API as a real Odoo IoT Box or POSbox. Point your POS configuration at it and the Odoo browser client does not know the difference.
This article covers why the substitution works at the protocol level, what devices Fakebox simulates, how to control test outcomes at runtime, and how it fits into a local Odoo POS development workflow.
Why the substitution works
Before getting into what Fakebox does, it is worth understanding why a software server can replace physical hardware at all.
The browser security wall
Odoo POS is a single-page web application. Browsers cannot access USB ports, serial ports, or any OS-level hardware interface directly. Every hardware interaction must go through HTTP. The IoT Box (or POSbox) exists precisely to bridge this gap: it is a local HTTP server running on the same network as the POS workstation, and the Odoo browser client calls it like any other API.
This design is what makes Fakebox possible. From Odoo’s perspective, the IoT Box is nothing more than a set of HTTP endpoints under /hw_proxy/. Any server that responds correctly to those endpoints is a valid IoT Box. Fakebox is that server.
The protocol
Every device interaction follows the same pattern: the Odoo browser sends an HTTP POST to /hw_proxy/<command> with a JSON-RPC payload, and the IoT Box responds with a JSON result. The Odoo codebase has no hardware-specific logic on the browser side. It calls the proxy, reads the result, and updates the POS UI accordingly.
Fakebox implements all the endpoints the Odoo POS browser calls. It does not emulate hardware at a driver level. It speaks the protocol.
Getting started
Fakebox is licensed AGPL-3.0 and hosted at gitlab.trobz.com/services/fakebox.
Install from source:
git clone https://gitlab.trobz.com/services/fakebox.git
cd fakebox
pip install -e .
Copy the sample configuration:
cp fakebox/config.sample.py fakebox/config.py
The defaults are 127.0.0.1:5000 for the HTTP server and 127.0.0.1:2121 for the FTP server (used by the Bizerba scale). Edit config.py to change them.
Start Fakebox:
fakebox
Verify it is running:
curl http://127.0.0.1:5000/hw_proxy/hello
Then set the IoT Box URL in your Odoo POS configuration to http://127.0.0.1:5000. The POS will handshake, query device status, and behave as if a real box is connected.
Device simulations
Payment terminal
The payment terminal simulation uses a deterministic amount rule by default: orders of 100 EUR or less result in a failed transaction; orders above 100 EUR succeed. This gives a reliable way to cover both outcomes in a test session without any configuration.
Amount <= 100 EUR -> transaction failed
Amount > 100 EUR -> transaction succeeded
For interactive control, set PAYMENT_TERMINAL_DRIVER=mock before starting Fakebox. In mock mode, the server prompts on stdin each time a transaction starts. Type ok for success or any other string to set a specific failure status. This is useful when you need to test a particular error message the POS displays.
Cashlogy automated cash drawer
The Cashlogy simulation implements the full transaction protocol that Odoo uses:
start_add_change: called when the customer transaction modal opensget_amount_accepted: polled every 0.1 seconds to update the amount displayed in the modalstop_acceptance: called when the modal is confirmed, either manually or automaticallydispense: called to release change to the customer
Opening and closing balance queries are also supported: get_total_amount returns the total cash in the machine, and get_inventory returns a coin and note breakdown. These are used by the opening and closing POS session flows when pos.config.cash_control is enabled.
All amounts are driven by output/values.ini:
[cashlogy]
amount_accepted = 50.00
amount_recycler = 200.00
amount_stacker = 50.00
amount_recycler and amount_stacker together represent the total cash in the machine (get_total_amount returns their sum). amount_accepted is what the customer has inserted so far.
Scales
Fakebox supports two scale types that Odoo POS uses in different configurations.
Toledo (POS-connected scale): responds to HTTP requests directly from the POS session. Fakebox implements scale_read, scale_price, scale_price_tare, scale_price_text, scale_price_tare_text, and reset_weight. Weight and price values are read from values.ini.
Bizerba (self-service scale): uses FTP to push weighed item data to a scale server. Fakebox runs a pyftpdlib FTP server on port 2121 that accepts uploads from the scale. Received files land in output/scale/ and are browsable at http://127.0.0.1:5000/scale. If your Bizerba model uses a non-UTF-8 encoding, configure it in values.ini:
[scale]
encoding = iso8859-1
Receipt printer and other peripherals
The receipt printer endpoints (print_receipt, print_xml_receipt) save each printed receipt as a file in output/receipts/. Browse them at http://127.0.0.1:5000/receipt to inspect exactly what the POS sends without a physical printer attached. This is useful for debugging receipt templates or verifying the XML structure of a custom receipt layout.
The customer display endpoints (send_text_customer_display, send_currency_data_customer_display) log their payloads. The barcode scanner endpoint (/hw_proxy/scanner) is a stub that returns a success response.
Controlling test outcomes with values.ini
The output/values.ini file is read on every relevant request, so changes take effect immediately without restarting the server or the POS session. This is the main lever for controlling what Fakebox returns at runtime.
Manual testing
While a POS session is open, edit values.ini to change the state Fakebox reports. Change amount_accepted and the Cashlogy modal updates on its next poll (every 0.1 seconds). Change the scale weight and the next weigh request returns the new value. No page reload, no server restart.
Scripted testing
A test script writes specific values to values.ini before triggering a POS action, then asserts on the result. No mocking, no monkey-patching, no test doubles in application code.
Example: simulating a Cashlogy session where the customer inserts the exact order amount:
echo "[cashlogy]
amount_accepted = 150.00
amount_recycler = 0.00
amount_stacker = 150.00" > output/values.ini
The POS polls get_amount_accepted, sees 150.00, auto-confirms the transaction (options.auto_accept = true), and validates the order. The full happy-path flow runs without any manual intervention.
CI pipelines
Fakebox starts with fakebox and has no external dependencies beyond the Python packages installed at setup. A CI job can start it as a background process, run the POS test suite against it, and tear it down. The values.ini file can be pre-written by the test setup to produce deterministic outcomes for each test case.
This makes hardware-dependent POS flows testable in environments where no physical IoT Box will ever be connected.
Conclusion
Fakebox works because the Odoo IoT Box is a protocol, not a device. Any HTTP server that responds correctly to hw_proxy endpoints is a valid substitute. Fakebox implements those endpoints, adds deterministic test controls via values.ini, and runs on a developer laptop with a single command.
Install it from gitlab.trobz.com/services/fakebox and point your POS configuration at http://127.0.0.1:5000.