RongtaBleSdk 0.2.1
dotnet add package RongtaBleSdk --version 0.2.1
NuGet\Install-Package RongtaBleSdk -Version 0.2.1
<PackageReference Include="RongtaBleSdk" Version="0.2.1" />
<PackageVersion Include="RongtaBleSdk" Version="0.2.1" />
<PackageReference Include="RongtaBleSdk" />
paket add RongtaBleSdk --version 0.2.1
#r "nuget: RongtaBleSdk, 0.2.1"
#:package RongtaBleSdk@0.2.1
#addin nuget:?package=RongtaBleSdk&version=0.2.1
#tool nuget:?package=RongtaBleSdk&version=0.2.1
<div align="center">
π¨οΈ RongtaBleSdk
Unofficial .NET MAUI SDK for printing labels over Bluetooth Low Energy on the Rongta RPP30
There is no official BLE UUID documentation for Rongta printers anywhere. This repo exists because someone needed to print a label on an RPP30 over BLE β and had to reverse-engineer it the hard way.
πΊπΈ English (you are here) Β· π§π· Ler em PortuguΓͺs
Why Β· Confirmed UUIDs Β· Install Β· Usage Β· Discovery tool Β· Contributing
</div>
π Why this project exists
The Rongta RPP30 is a portable thermal label printer with "dual mode" Bluetooth (classic + BLE). If you want to print on it from:
- iOS β Apple doesn't allow classic Bluetooth (SPP) in third-party apps. BLE is the only option.
- An app already built on a BLE stack (Shiny.BluetoothLE, Plugin.BLE, CoreBluetooth, Web Bluetooth, ESP32...) β without depending on Rongta's native SDK.
...you hit a wall: Rongta does not publish the BLE service/characteristic UUIDs anywhere. Their official SDK (Android/iOS/Windows/Linux) only documents the classic Bluetooth flow (BluetoothEdrConfigBean, standard SPP UUID 00001101-...). The BLE classes (BleDriver, BleConfigBean) exist inside the .jar and use the open-source FastBLE library under the hood β but there is no documented example anywhere in the official manual.
So we connected to a real RPP30, enumerated its actual GATT table, and confirmed everything by printing a real label. This repo is the result.
β Confirmed for real (not a guess)
Unlike "probable" UUIDs copied from forum threads, the values below were validated on physical hardware:
- Scanned via
Windows.Devices.Bluetooth.Advertisementβ deviceRPP30-C860found. - Connected and enumerated every real service/characteristic over GATT (
GetGattServicesAsync+GetCharacteristicsAsync). - Sent a test CPCL command, chunked into 20-byte blocks, through the write characteristic.
- The printer printed the label. βοΈ
π Confirmed UUIDs (RPP30, "BLE-TX" firmware)
| Role | UUID |
|---|---|
| Service (BLE UART β CC41/HM-10/JDY chip family) | 49535343-fe7d-4ae5-8fa9-9fafd205e455 |
Write characteristic (WRITE + WRITE_NO_RESPONSE) |
49535343-8841-43f4-a8d4-ecbe34729bb3 |
| Notify characteristic (status) | 49535343-1e4d-4bd9-ba61-23c647249616 |
β οΈ These may vary by batch/firmware revision. Before blindly trusting these values on your unit, run the discovery tool included in this repo β it takes under a minute.
<details> <summary><strong>Other services exposed by the tested RPP30 (not used by this SDK, documented for completeness)</strong></summary>
0000ff80-0000-1000-8000-00805f9b34fb
0000ff82 [WRITE, WRITE_NO_RESPONSE]
0000ff81 [NOTIFY]
0000ff00-0000-1000-8000-00805f9b34fb
0000ff02 [WRITE, WRITE_NO_RESPONSE]
0000ff01 [NOTIFY]
0000ff03 [NOTIFY]
0000ff10-0000-1000-8000-00805f9b34fb
0000ff11 [WRITE_NO_RESPONSE, NOTIFY]
0000ff12 [WRITE_NO_RESPONSE, NOTIFY]
Likely variations/duplicates of the same serial transport, exposed for compatibility with different apps. Not investigated further.
</details>
π What's new in 0.2.0
Field-tested improvements ported from a production CPCL/BLE printing pipeline (a livestock-weighing MAUI app that prints thousands of labels a day):
- MTU negotiation β requests a 512-byte MTU on connect (Android), auto-sizing the write chunk instead of a fixed 20 bytes. Falls back safely when negotiation isn't supported (iOS/Windows negotiate on their own).
- Write retries β each chunk gets up to 3 attempts with backoff before the send fails, and a disconnect mid-print now throws a clear
IOExceptioninstead of hanging. - Multi-UUID discovery β
RongtaBlePrinternow tries a list of known UUID pairs (the confirmed RPP30 one plus a few common UART-BLE variants seen on similar Chinese printer chips) and falls back to "first writable characteristic" if none match β more likely to work out of the box on a different batch or a different Rongta model. - Auto label height β
CpclLabelBuilder.CreateAutoHeightMm(width, ...)calculates the label height from the content you add (text/barcode/QR/image Y positions) instead of requiring you to know it up front. - Image printing β
AddImage(...)converts any PNG/JPG (via SkiaSharp) to the CPCLEGmonochrome command, so you can print logos or graphics, not just text/barcodes/QR. - Diacritics stripping β accented characters are automatically stripped before sending, since CPCL on these printers is effectively ASCII-only.
π§Ύ Print command language: CPCL
The RPP30 accepts both CPCL and ESC/POS (switchable from the physical menu: hold Power + Feed β Cmd Mode: CPCL/ESC). This SDK generates CPCL β confirmed by printing a real label over BLE. TSPL and ZPL have not been tested (see Known limitations).
π¦ Installation
dotnet add package RongtaBleSdk
or in your .csproj:
<PackageReference Include="RongtaBleSdk" Version="0.2.1" />
<PackageReference Include="Shiny.BluetoothLE" Version="4.0.1" />
<PackageReference Include="SkiaSharp" Version="3.119.4" />
Shiny.BluetoothLE and SkiaSharp come in transitively, but pinning your own versions avoids surprises across MAUI upgrades.
Register in MauiProgram.cs:
var builder = MauiApp.CreateBuilder();
builder.Services.AddBluetoothLE(); // BLE transport (Shiny.BluetoothLE)
builder.Services.AddRongtaBlePrinter(); // RongtaBlePrinter as a singleton
π Usage
public class LabelService(RongtaBlePrinter printer)
{
public async Task PrintWeighingAsync(string identifier, double weightKg)
{
// Scans until it finds an "RPP30-XXXX" device and connects
await printer.ScanAndConnectAsync();
var label = CpclLabelBuilder
.CreateMm(widthMm: 120, heightMm: 80)
.AddText(30, 30, "CELMI", font: 5)
.AddText(30, 80, $"ID: {identifier}")
.AddText(30, 120, $"Weight: {weightKg:F2} kg")
.AddQrCode(30, 160, identifier);
await printer.PrintAsync(label);
await printer.DisconnectAsync();
}
}
Connecting to a peripheral already scanned by your own UI
// if you already have your own scan/pairing screen (e.g. reusing IBleManager directly)
await printer.ConnectAsync(chosenPeripheral);
await printer.PrintAsync(label);
Sending raw bytes (e.g. a different command dialect)
byte[] rawCommand = Encoding.ASCII.GetBytes("! 0 200 200 210 1\r\nTEXT 4 0 30 30 HELLO\r\nFORM\r\nPRINT\r\n");
await printer.SendAsync(rawCommand);
π§© CpclLabelBuilder API
| Method | Description |
|---|---|
CreateMm(width, height, qty, gapMm) |
Creates a fixed-height label from a size in millimeters (203dpi β 8 dots/mm) |
CreateDots(width, height, qty, gapDots) |
Same as above, but the size is already in dots |
CreateAutoHeightMm(width, qty, gapMm) |
Creates a label whose height is calculated automatically from whatever content you add (text/barcode/QR/image Y positions) β no need to know it up front |
CreateAutoHeightDots(width, qty, gapDots) |
Same as above, width already in dots |
AddText(x, y, text, font, size) |
Adds a line of text |
AddBarcode(x, y, data, type, height, ...) |
Adds a 1D barcode (CODE128, EAN13, etc.) |
AddQrCode(x, y, data, cellSize) |
Adds a QR code |
AddLine(x, y, length, thickness) |
Line/separator |
AddImage(x, y, imageBytes, maxWidthDots, maxHeightDots) |
Converts a PNG/JPG (via SkiaSharp) to the CPCL EG command β logos, graphics, anything bitmap |
AddRawCommand(cpclBlock) |
Appends one or more raw CPCL command lines for anything not covered by the fluent API; still parsed for automatic height tracking |
Build() |
Generates the final CPCL bytes (header, TONE/SETMAG, diacritics stripped, FORM/PRINT) ready for SendAsync |
RongtaBlePrinter also exposes DetectedWriteEndpoint after connecting, so you can log/inspect which service/characteristic pair actually worked on your unit.
π Discovery tool
A Windows console app (Windows.Devices.Bluetooth) that scans BLE, connects to the printer and lists every real service/characteristic with its properties (WRITE / WRITE_NO_RESPONSE / NOTIFY / READ) β the exact process used to confirm the UUIDs in this README. Run it if:
- Your RPP30 is a different batch/firmware and the UUIDs above didn't match;
- You want to adapt this SDK to another Rongta model.
cd tools/RongtaBleDiscovery
dotnet run -c Release
It scans for 15s, connects to the first device whose name contains RPP/RONGTA/Printer (or lets you type the address manually) and prints the full GATT tree to the console β optionally also sending a CPCL test label to validate the write characteristic it found.
πΊοΈ Architecture
βββββββββββββββββββββββββββ
β Your MAUI App β
β (Android / iOS / Mac) β
ββββββββββββββ¬ββββββββββββ
β DI: RongtaBlePrinter
βΌ
βββββββββββββββββββββββββββ
β RongtaBleSdk β
β βββββββββββββββββββββ β
β β CpclLabelBuilder β β text, barcode, QR β CPCL bytes
β βββββββββββββββββββββ β
β βββββββββββββββββββββ β
β β RongtaBlePrinter β β scan, connect, chunk & write
β βββββββββββ¬ββββββββββ β
ββββββββββββββΌββββββββββββ
β IBleManager / IPeripheral
βΌ
βββββββββββββββββββββββββββ
β Shiny.BluetoothLE β abstracts Android/iOS/Windows
ββββββββββββββ¬ββββββββββββ
β GATT write (service 49535343-fe7d-...)
βΌ
βββββββββββββββββββββββββββ
β Rongta RPP30 (BLE) β
βββββββββββββββββββββββββββ
β οΈ Known limitations / next steps
- β Tested printing in CPCL. β TSPL/ESC/ZPL not tested in this project (the RPP30 supports all four, switchable from the physical menu).
- Tested on a single physical unit only ("BLE-TX" firmware, name
RPP30-C860). Contributions confirming/correcting UUIDs on other batches are welcome. - iOS: the API is abstracted by Shiny.BluetoothLE and the package builds and ships for
net10.0-ios/net10.0-maccatalyst, but the BLE flow itself hasn't been validated on a real iPhone/Mac yet. - Diacritics are stripped and CPCL is sent as ASCII β good enough for Latin-script labels, but the printer's own codepage settings (
CP850/CP1252/etc) aren't configurable from the SDK yet.
π¦ Releasing
Publishing to NuGet.org uses Trusted Publishing β no API key is stored anywhere. .github/workflows/publish.yml requests a short-lived OIDC token from GitHub Actions, exchanges it for a temporary NuGet API key, and pushes the package. It runs on workflow_dispatch or on pushing a v* tag.
π€ Contributing
Tested on another Rongta model or a different RPP30 batch? Please open an issue with:
- The full output of the discovery tool run against your unit;
- The exact device name advertised (
RPP30-XXXX); - Whether the UUIDs in this README matched or not.
PRs for TSPL/ESC/ZPL support, MTU negotiation, or iOS testing are very welcome.
π License
MIT β see LICENSE.
<div align="center">
Built by reverse-engineering what Rongta never documented, for anyone else who also needs to print on an RPP30 over BLE.
</div>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 is compatible. net10.0-ios26.0 is compatible. net10.0-maccatalyst26.0 is compatible. net10.0-windows10.0.17763 is compatible. |
-
net10.0-android36.0
- Shiny.BluetoothLE (>= 4.0.1)
- SkiaSharp (>= 3.119.4)
-
net10.0-ios26.0
- Shiny.BluetoothLE (>= 4.0.1)
- SkiaSharp (>= 3.119.4)
-
net10.0-maccatalyst26.0
- Shiny.BluetoothLE (>= 4.0.1)
- SkiaSharp (>= 3.119.4)
-
net10.0-windows10.0.17763
- Shiny.BluetoothLE (>= 4.0.1)
- SkiaSharp (>= 3.119.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.1 | 126 | 8/6/2026 |