Skip to content

Commit 3e7610d

Browse files
authored
Merge pull request #20 from GhostTypes/feat/printer-discovery-multicast-fix
feat: improve printer discovery with multicast fix and type safety
2 parents 2678617 + 4bf7acd commit 3e7610d

11 files changed

Lines changed: 2089 additions & 366 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Raw API responses (`FFPrinterDetail` in `src/models/ff-models.ts`) are transform
4646

4747
- `NetworkUtils` (`src/api/network/NetworkUtils.ts`) — Response validation helpers; checks `GenericResponse.code` for success.
4848
- `FNetCode` (`src/api/network/FNetCode.ts`) — Network code constants.
49-
- `FlashForgePrinterDiscovery` (`src/api/PrinterDiscovery.ts`) — UDP broadcast discovery on port 48899, parses binary response buffers at fixed offsets for printer name and serial number.
49+
- `PrinterDiscovery` (`src/api/PrinterDiscovery.ts`) — Universal UDP multicast/broadcast discovery supporting all FlashForge models (AD5X, 5M, 5M Pro, Adventurer 4, Adventurer 3) with multi-protocol response parsing (276-byte modern, 140-byte legacy).
5050

5151
### TCP Response Parsers
5252

docs/MIGRATION_GUIDE.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Printer Discovery API Migration Guide
2+
3+
**Version:** 2.0.0
4+
**Date:** 2025-02-08
5+
**Breaking Changes:** Yes
6+
7+
## Overview
8+
9+
The printer discovery API has been completely rewritten to support all FlashForge printer models (AD5X, 5M, 5M Pro, Adventurer 4, Adventurer 3) with multi-protocol UDP discovery. The legacy API has been removed.
10+
11+
## What Changed
12+
13+
| Old API | New API |
14+
|---------|---------|
15+
| `FlashForgePrinterDiscovery` | `PrinterDiscovery` |
16+
| `FlashForgePrinter` | `DiscoveredPrinter` (interface) |
17+
| `discoverPrintersAsync(timeout, idleTimeout, maxRetries)` | `discover({ timeout, idleTimeout, maxRetries })` |
18+
| `isAD5X?: boolean` | `model: PrinterModel` |
19+
| Limited metadata (name, serial, IP) | Complete metadata (model, ports, status, etc.) |
20+
21+
## Migration Examples
22+
23+
### Basic Discovery
24+
25+
**Before (v1.x):**
26+
```typescript
27+
import { FlashForgePrinterDiscovery, FlashForgePrinter } from '@ghosttypes/ff-api';
28+
29+
const discovery = new FlashForgePrinterDiscovery();
30+
const printers: FlashForgePrinter[] = await discovery.discoverPrintersAsync(10000, 1500, 3);
31+
32+
printers.forEach(printer => {
33+
console.log(`${printer.name} - ${printer.ipAddress}`);
34+
if (printer.isAD5X) {
35+
console.log(' AD5X detected');
36+
}
37+
});
38+
```
39+
40+
**After (v2.x):**
41+
```typescript
42+
import { PrinterDiscovery, PrinterModel, type DiscoveredPrinter } from '@ghosttypes/ff-api';
43+
44+
const discovery = new PrinterDiscovery();
45+
const printers: DiscoveredPrinter[] = await discovery.discover({
46+
timeout: 10000,
47+
idleTimeout: 1500,
48+
maxRetries: 3
49+
});
50+
51+
printers.forEach(printer => {
52+
console.log(`${printer.model}: ${printer.name} - ${printer.ipAddress}`);
53+
if (printer.model === PrinterModel.AD5X) {
54+
console.log(' AD5X detected');
55+
}
56+
});
57+
```
58+
59+
### Model Detection
60+
61+
**Before:**
62+
```typescript
63+
if (printer.isAD5X) {
64+
// Handle AD5X
65+
} else {
66+
// Handle other models
67+
}
68+
```
69+
70+
**After:**
71+
```typescript
72+
switch (printer.model) {
73+
case PrinterModel.AD5X:
74+
// Handle AD5X
75+
break;
76+
case PrinterModel.Adventurer5MPro:
77+
// Handle 5M Pro
78+
break;
79+
case PrinterModel.Adventurer5M:
80+
// Handle 5M
81+
break;
82+
case PrinterModel.Adventurer4:
83+
// Handle Adventurer 4
84+
break;
85+
case PrinterModel.Adventurer3:
86+
// Handle Adventurer 3
87+
break;
88+
default:
89+
// Unknown model
90+
break;
91+
}
92+
```
93+
94+
### Accessing Additional Properties
95+
96+
**Before:**
97+
```typescript
98+
const printer: FlashForgePrinter = {
99+
name: 'AD5X',
100+
serialNumber: 'SN123',
101+
ipAddress: '192.168.1.100',
102+
isAD5X: true
103+
};
104+
```
105+
106+
**After:**
107+
```typescript
108+
const printer: DiscoveredPrinter = {
109+
model: PrinterModel.AD5X,
110+
protocolFormat: DiscoveryProtocol.Modern,
111+
name: 'AD5X',
112+
ipAddress: '192.168.1.100',
113+
commandPort: 8899,
114+
serialNumber: 'SN123',
115+
eventPort: 8898,
116+
vendorId: 0x2B71,
117+
productId: 0x0024,
118+
productType: 0x5A02,
119+
statusCode: 0,
120+
status: PrinterStatus.Ready
121+
};
122+
```
123+
124+
### Custom Discovery Options
125+
126+
**Before:**
127+
```typescript
128+
await discovery.discoverPrintersAsync(5000, 1000, 1);
129+
```
130+
131+
**After:**
132+
```typescript
133+
await discovery.discover({
134+
timeout: 5000,
135+
idleTimeout: 1000,
136+
maxRetries: 1,
137+
useMulticast: true,
138+
useBroadcast: true,
139+
ports: [19000, 8899]
140+
});
141+
```
142+
143+
## New Features
144+
145+
### Event-Based Monitoring
146+
147+
The new API supports continuous monitoring with events:
148+
149+
```typescript
150+
const discovery = new PrinterDiscovery();
151+
const monitor = discovery.monitor({ timeout: 30000 });
152+
153+
monitor.on('discovered', (printer: DiscoveredPrinter) => {
154+
console.log(`✓ Found: ${printer.model} - ${printer.name}`);
155+
});
156+
157+
monitor.on('end', () => {
158+
console.log('Discovery complete');
159+
});
160+
161+
monitor.on('error', (error: Error) => {
162+
console.error('Discovery error:', error);
163+
});
164+
```
165+
166+
### Printer Status
167+
168+
The new API includes printer status from discovery:
169+
170+
```typescript
171+
const printers = await discovery.discover();
172+
if (printers.length > 0) {
173+
const printer = printers[0];
174+
175+
if (printer.status === PrinterStatus.Ready) {
176+
console.log('Printer is ready to print');
177+
} else if (printer.status === PrinterStatus.Busy) {
178+
console.log('Printer is busy');
179+
} else if (printer.status === PrinterStatus.Error) {
180+
console.log('Printer has an error');
181+
}
182+
}
183+
```
184+
185+
### Multi-Protocol Support
186+
187+
The new API automatically handles multiple protocols:
188+
189+
- **Modern Protocol (276-byte)**: AD5X, 5M, 5M Pro
190+
- **Legacy Protocol (140-byte)**: Adventurer 3, Adventurer 4
191+
192+
```typescript
193+
printers.forEach(printer => {
194+
if (printer.protocolFormat === DiscoveryProtocol.Modern) {
195+
console.log('Modern printer - full metadata available');
196+
console.log(` Serial: ${printer.serialNumber}`);
197+
console.log(` HTTP API: ${printer.ipAddress}:${printer.eventPort}`);
198+
} else if (printer.protocolFormat === DiscoveryProtocol.Legacy) {
199+
console.log('Legacy printer - basic metadata');
200+
}
201+
});
202+
```
203+
204+
## Type Reference
205+
206+
### PrinterModel Enum
207+
208+
```typescript
209+
enum PrinterModel {
210+
AD5X = 'AD5X',
211+
Adventurer5M = 'Adventurer5M',
212+
Adventurer5MPro = 'Adventurer5MPro',
213+
Adventurer4 = 'Adventurer4',
214+
Adventurer3 = 'Adventurer3',
215+
Unknown = 'Unknown'
216+
}
217+
```
218+
219+
### DiscoveryProtocol Enum
220+
221+
```typescript
222+
enum DiscoveryProtocol {
223+
Modern = 'modern', // 276-byte responses
224+
Legacy = 'legacy' // 140-byte responses
225+
}
226+
```
227+
228+
### PrinterStatus Enum
229+
230+
```typescript
231+
enum PrinterStatus {
232+
Ready = 0,
233+
Busy = 1,
234+
Error = 2,
235+
Unknown = 3
236+
}
237+
```
238+
239+
### DiscoveredPrinter Interface
240+
241+
```typescript
242+
interface DiscoveredPrinter {
243+
model: PrinterModel;
244+
protocolFormat: DiscoveryProtocol;
245+
name: string;
246+
ipAddress: string;
247+
commandPort: number;
248+
serialNumber?: string; // Modern protocol only
249+
eventPort?: number; // Modern protocol only (typically 8898)
250+
vendorId?: number;
251+
productId?: number;
252+
productType?: number; // Modern protocol only
253+
statusCode?: number;
254+
status?: PrinterStatus;
255+
}
256+
```
257+
258+
### DiscoveryOptions Interface
259+
260+
```typescript
261+
interface DiscoveryOptions {
262+
timeout?: number; // Default: 10000
263+
idleTimeout?: number; // Default: 1500
264+
maxRetries?: number; // Default: 3
265+
useMulticast?: boolean; // Default: true
266+
useBroadcast?: boolean; // Default: true
267+
ports?: number[]; // Default: [8899, 19000, 48899]
268+
}
269+
```
270+
271+
## Full Migration Checklist
272+
273+
- [ ] Update imports from `FlashForgePrinterDiscovery` to `PrinterDiscovery`
274+
- [ ] Change `discoverPrintersAsync()` calls to `discover()`
275+
- [ ] Update parameter style from positional to options object
276+
- [ ] Replace `FlashForgePrinter` type with `DiscoveredPrinter`
277+
- [ ] Replace `isAD5X` boolean checks with `model` enum comparisons
278+
- [ ] Update any destructuring to use new property names
279+
- [ ] Remove `toString()` calls (not available on interface)
280+
- [ ] Test with all printer models you support
281+
- [ ] Update any documentation or examples
282+
283+
## Need Help?
284+
285+
- **New API Documentation**: See `docs/README.md` and `docs/clients.md`
286+
- **Full Specification**: See `docs/specs/printer-discovery.md`
287+
- **Type Definitions**: See `src/models/PrinterDiscovery.ts`
288+
- **Example Usage**: See test files in `src/api/PrinterDiscovery.test.ts`

docs/README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ pnpm add @ghosttypes/ff-api
3030
To start interacting with a printer, you first need to discover it on your local network.
3131

3232
```typescript
33-
import { FlashForgePrinterDiscovery } from '@ghosttypes/ff-api';
33+
import { PrinterDiscovery } from '@ghosttypes/ff-api';
3434

35-
const discovery = new FlashForgePrinterDiscovery();
36-
const printers = await discovery.discoverPrintersAsync();
35+
const discovery = new PrinterDiscovery();
36+
const printers = await discovery.discover();
3737

3838
printers.forEach(printer => {
39-
console.log(`Found printer: ${printer.name} at ${printer.ipAddress}`);
39+
console.log(`Found ${printer.model}: ${printer.name} at ${printer.ipAddress}`);
40+
if (printer.serialNumber) {
41+
console.log(` Serial: ${printer.serialNumber}`);
42+
}
4043
});
4144
```
4245

docs/clients.md

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,61 @@ Sets the extruder target temperature.
123123

124124
---
125125

126-
## FlashForgePrinterDiscovery
126+
## PrinterDiscovery
127127

128-
A utility class for finding printers on the local network.
128+
A utility class for finding FlashForge printers on the local network via UDP multicast/broadcast. Supports all FlashForge models including AD5X, 5M, 5M Pro, Adventurer 4, and Adventurer 3.
129+
130+
### Constructor
131+
132+
```typescript
133+
constructor()
134+
```
129135

130136
### Methods
131137

132-
#### `discoverPrintersAsync()`
138+
#### `discover()`
133139

134140
```typescript
135-
public async discoverPrintersAsync(timeoutMs: number = 10000, idleTimeoutMs: number = 1500, maxRetries: number = 3): Promise<FlashForgePrinter[]>
141+
public async discover(options?: DiscoveryOptions): Promise<DiscoveredPrinter[]>
136142
```
137143

138-
Broadcasts a discovery packet via UDP and listens for responses.
144+
Discovers printers on the local network using UDP multicast and broadcast.
139145

140-
- **`timeoutMs`**: Max time to wait for responses.
141-
- **`idleTimeoutMs`**: Time to wait after the last response before returning.
142-
- **`maxRetries`**: Number of broadcast attempts if no printers are found initially.
146+
**Options:**
147+
- **`timeout`** (number): Total time to wait for responses (default: 10000ms)
148+
- **`idleTimeout`** (number): Time to wait after last response (default: 1500ms)
149+
- **`maxRetries`** (number): Maximum retry attempts (default: 3)
150+
- **`useMulticast`** (boolean): Use multicast discovery (default: true)
151+
- **`useBroadcast`** (boolean): Use subnet broadcast discovery (default: true)
152+
- **`ports`** (number[]): Specific ports to scan (default: [8899, 19000, 48899])
143153

144-
**Returns:** An array of `FlashForgePrinter` objects containing IP, Serial, and Name.
154+
**Returns:** An array of `DiscoveredPrinter` objects with comprehensive printer information.
155+
156+
#### `monitor()`
157+
158+
```typescript
159+
public monitor(options?: DiscoveryOptions): EventEmitter
160+
```
161+
162+
Starts continuous monitoring for printers, emitting events as printers are discovered.
163+
164+
**Returns:** EventEmitter that emits:
165+
- **`discovered`**: Emitted for each new printer found
166+
- **`end`**: Emitted when monitoring completes
167+
- **`error`**: Emitted on errors
168+
169+
### Example
170+
171+
```typescript
172+
import { PrinterDiscovery } from '@ghosttypes/ff-api';
173+
174+
const discovery = new PrinterDiscovery();
175+
const printers = await discovery.discover({ timeout: 5000 });
176+
177+
printers.forEach(printer => {
178+
console.log(`${printer.model}: ${printer.name}`);
179+
console.log(` IP: ${printer.ipAddress}:${printer.commandPort}`);
180+
console.log(` Serial: ${printer.serialNumber || 'N/A'}`);
181+
console.log(` Status: ${printer.status}`);
182+
});
183+
```

0 commit comments

Comments
 (0)