Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ go.work.sum
!docs/TEMPLATE.md
!docs/PROFILE_NITPICKS.md
!docs/CONN_NITPICKS.md
docs/QR_NITPICKS.md
!docs/QR_NITPICKS.md
!docs/RAW_COMMAND.md
!docs/USAGE.md
!docs/SECURITY.md


!*.php
Expand Down
37 changes: 37 additions & 0 deletions api/v1/DOCUMENT_V1.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,43 @@ Corta el papel:
| `mode` | string | | Tipo de corte | | full, partial |
| `feed` | integer | | Líneas a avanzar antes del corte | 2 | 0-255 |

### 9. Raw Command

Envía bytes directamente a la impresora sin procesamiento:

⚠️ **ADVERTENCIA**: Comando avanzado. Uso incorrecto puede dañar la configuración de la impresora.

```json
{
"type": "raw",
"data": {
"hex": "1B 40",
"format": "hex",
"comment": "Reset printer",
"safe_mode": false
}
}
```

| Campo | Tipo | Requerido | Descripción | Default | Valores |
|-------------|---------|-----------|-------------------------------------|---------|-------------|
| `hex` | string | ✓ | Bytes en formato especificado | | |
| `format` | string | | Formato de entrada | hex | hex, base64 |
| `comment` | string | | Descripción del comando | | |
| `safe_mode` | boolean | | Habilitar validaciones de seguridad | false | |

**Formatos de Hex Soportados:**

- Espacios: `"1B 40"`
- Sin espacios: `"1B40"`
- Con comas: `"1B,40"`
- Con prefijo: `"0x1B 0x40"`

**Límites:**

- Máximo 4096 bytes por comando
- Solo caracteres hexadecimales válidos

## Ejemplo Completo

```json
Expand Down
39 changes: 38 additions & 1 deletion api/v1/document.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"cut",
"qr",
"table",
"barcode"
"barcode",
"raw"
],
"description": "Command type"
},
Expand Down Expand Up @@ -126,6 +127,9 @@
},
{
"$ref": "#/definitions/BarcodeCommand"
},
{
"$ref": "#/definitions/RawCommand"
}
]
}
Expand Down Expand Up @@ -469,6 +473,39 @@
}
}
},
"RawCommand": {
"type": "object",
"required": [
"hex"
],
"properties": {
"hex": {
"type": "string",
"description": "Raw bytes in specified format (hexadecimal or base64)",
"minLength": 1,
"maxLength": 8192
},
"format": {
"type": "string",
"enum": [
"hex",
"base64"
],
"description": "Format of the hex field",
"default": "hex"
},
"comment": {
"type": "string",
"description": "Optional description of what this raw command does",
"maxLength": 500
},
"safe_mode": {
"type": "boolean",
"description": "Enable safety checks to block known dangerous commands",
"default": false
}
}
},
"TableCommand": {
"type": "object",
"required": [
Expand Down
90 changes: 90 additions & 0 deletions docs/RAW_COMMAND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Raw Command Documentation

## ⚠️ Important Limitations

1. **Write-Only Operation**: Raw commands are unidirectional. Commands that expect responses
(like status queries) will NOT return data through this interface.

2. **No State Tracking**: Raw commands bypass all state management. The library cannot track
changes made by raw commands. It is up to the printer and you to ensure correct state.

3. **Buffer Responsibility**: You are responsible for any data left in read buffers by
query commands.

4. **Encoding Management**: Raw commands do not handle text encoding. Ensure any text data is
correctly encoded before sending.

## Security Modes

### Standard Mode (safe_mode: false)

- Commands execute without validation
- Full responsibility on the developer
- Suitable for production with tested commands

### Safe Mode (safe_mode: true)

- Known dangerous commands are BLOCKED
- Execution stops with error
- Recommended for development/testing

## Common Escape Sequences

### Cash Drawer

```json
{
"type": "raw",
"data": {
"hex": "1B 70 00 32 64",
"comment": "Open drawer pin 2, 100ms pulse"
}
}
```

### Beeper/Buzzer

```json
{
"type": "raw",
"data": {
"hex": "07",
"comment": "Single beep"
}
}
```

### Chinese Printer Compatibility

```json
{
"type": "raw",
"data": {
"hex": "1B 21 00",
"comment": "Reset text style - workaround for GP-5890X"
}
}
```

## Blocked Commands in Safe Mode

| Command | Hex | Risk Level | Description |
|---------|------------|------------|-----------------------------------|
| ESC @ | `1B 40` | HIGH | Full reset - clears ALL settings |
| ESC = n | `1B 3D 00` | HIGH | Disable printer |
| DLE ENQ | `10 05` | MEDIUM | Status query - leaves unread data |
| ESC p | `1B 70` | LOW | Cash drawer - physical activation |

## Builder Usage Examples

```go
// Safe development usage
builder.AddRawSafe("1B 40", "Reset attempt") // Will be BLOCKED

// Production usage
builder.AddRaw("1B 70 00 32 64", "Open drawer") // Executes

// Convenience methods (bypass safety)
builder.AddPulse() // Opens drawer
builder.AddBeep(3) // Three beeps
```
87 changes: 87 additions & 0 deletions examples/beeper/beeper_example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Package main demuestra cómo enviar comandos RAW no estándar (específicos del fabricante).
//
// Caso de uso: Muchas impresoras genéricas/chinas utilizan 'ESC B' (1B 42) para el bíper,
// mientras que el estándar oficial ESC/POS no esta definido.
// Este ejemplo muestra cómo la librería permite enviar bytes crudos para cubrir estos casos de borde.
package main

import (
"log"
"time"

"github.com/adcondev/pos-printer/pkg/composer"
"github.com/adcondev/pos-printer/pkg/connection"
"github.com/adcondev/pos-printer/pkg/profile"
"github.com/adcondev/pos-printer/pkg/service"
)

func main() {
// 1. Configuración del perfil
// Usamos un perfil genérico de 80mm (EC-PM-80250)
prof := profile.CreateECPM80250()

// 2. Establecer conexión (Windows en este ejemplo)
// Nota: Asegúrate de que el nombre de la impresora coincida con el de tu sistema
conn, err := connection.NewWindowsPrintConnector(prof.Model)
if err != nil {
log.Fatalf("Error de conexión: %v", err)
}
defer func() {
if err := conn.Close(); err != nil {
log.Printf("Error cerrando conexión: %v", err)
}
}()

// 3. Inicializar protocolo y servicio
proto := composer.NewEscpos()
printer, err := service.NewPrinter(proto, prof, conn)
if err != nil {
log.Panicf("Error creando servicio de impresora: %v", err)
}
defer func() {
if err := printer.Close(); err != nil {
log.Printf("Error cerrando servicio: %v", err)
}
}()

// Inicializar impresora (resetea buffer y estados)
if err := printer.Initialize(); err != nil {
log.Panic(err)
}

log.Println("Iniciando prueba de Beeper Genérico...")

// 4. Construcción del Comando RAW
// -------------------------------------------------------------------------
// ADVERTENCIA DE COMPATIBILIDAD:
// El estándar ESC/POS define 'ESC ( A' (Pulse) para el buzzer.
// Sin embargo, este hardware específico usa 'ESC B' (no existe es ESC/POS).
//
// Formato: ESC B n t
// Hex: 1B 42 09 02
// -------------------------------------------------------------------------
// 1B 42 -> Cabecera del comando (ESC B)
// 09 -> n: Número de repeticiones (9 veces)
// 02 -> t: Duración/Intervalo (factor de tiempo, aprox 100ms * t)
// -------------------------------------------------------------------------
beeperCmd := []byte{0x1B, 0x42, 0x09, 0x02}

// Enviar bytes directamente (bypass del protocolo estándar)
if err := printer.Write(beeperCmd); err != nil {
log.Panicf("Fallo al enviar comando raw: %v", err)
}

// Pequeña pausa para permitir que el sonido termine antes de cortar
time.Sleep(2 * time.Second)

// 5. Imprimir confirmación visual usando métodos estándar
if err := printer.PrintLine("Prueba de sonido completada."); err != nil {
log.Printf("Error imprimiendo texto: %v", err)
}

if err := printer.PartialFeedAndCut(1); err != nil {
log.Printf("Error cortando papel: %v", err)
}

log.Println("Finalizado con éxito.")
}
97 changes: 97 additions & 0 deletions examples/document/raw/kiosk_turns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"version": "1.0",
"profile": {
"model": "Generic"
},
"commands": [
{
"type": "text",
"data": {
"content": {
"text": "CONFIGURANDO KIOSKO DE TURNOS...\n",
"align": "center"
}
}
},
{
"type": "raw",
"data": {
"hex": "1D 43 31 01 00 E7 03 01 01",
"comment": "SETUP: Contador inicia en 1, max 999"
}
},
{
"type": "raw",
"data": {
"hex": "1D 3A",
"comment": "Start Macro Rec"
}
},
{
"type": "text",
"data": {
"content": {
"text": "FARMACIA CENTRAL\nPor favor espere su turno",
"align": "center",
"new_line": true
}
}
},
{
"type": "text",
"data": {
"content": {
"text": "TURNO N.",
"content_style": {
"size": "2x2",
"bold": true
},
"align": "center"
}
}
},
{
"type": "raw",
"data": {
"hex": "1D 63",
"comment": "PRINT COUNTER VARIABLE"
}
},
{
"type": "feed",
"data": {
"lines": 2
}
},
{
"type": "text",
"data": {
"content": {
"text": "Gracias por su visita\n",
"align": "center"
}
}
},
{
"type": "cut",
"data": {
"mode": "partial",
"feed": 1
}
},
{
"type": "raw",
"data": {
"hex": "1D 3A",
"comment": "End Macro Rec"
}
},
{
"type": "raw",
"data": {
"hex": "1D 5E FF 0A 01",
"comment": "EXECUTE: 255 veces, espera botón FEED"
}
}
]
}
Loading