# Bambu Node

> [!CAUTION]  
> 🚧 This library is still in the making. PRs and other feature requests are welcome.

A node.js library for connecting to and receiving data from Bambu Lab printers through
their MQTT servers.

- Every command & response field is documented & typed.
- Easily (and safely\*) construct commands & manage responses.
- Full async support! `client#executeCommand` waits until the command completion is
  verified by the printer.

## Getting Started

### Prerequisites

Make sure you have the following installed:

- Node.js
- NPM
- TypeScript

> [!CAUTION]  
> TypeScript is highly recommended for this package due to the type safety it provides.
> This is especially important in use cases like this project where the library
> communicates with external hardware which can very well come with property damage. And
> even with TypeScript, I am not liable for any such damages as stated in the
> [license](LICENSE).

### Installation

```bash
npm install bambu-node
```

### Example Usage

### Understanding the Protocol

#### Sequence ID

The sequence_id is a critical component of the Bambu Lab printer communication protocol. It ensures reliable, ordered, and secure command processing between the client and printer.

##### How It Works

- Each command sent to the printer includes a unique sequence_id
- Valid sequence_ids must be between 20000 and 30000
- The sequence_id must be incremented for each new command
- The printer returns responses with the same sequence_id as the original command

##### Why It's Critical

1. **Command Validation**
   - The sequence_id range acts as a security measure
   - Only commands with valid sequence_ids are processed
   - Helps prevent unauthorized command injection

2. **Command-Response Matching**
   - Responses include the original command's sequence_id
   - Allows matching responses to their originating commands
   - Essential for asynchronous command processing

3. **Error Handling**
   - Error responses are matched to commands via sequence_id
   - Enables proper error attribution and handling
   - Critical for temperature and safety-related commands

4. **State Management**
   - Maintains proper command ordering
   - Prevents command duplication
   - Ensures reliable command acknowledgment

##### Implementation Requirements

- Always increment sequence_id for each new command
- Never reuse sequence_ids within a session
- Validate sequence_ids in responses match sent commands
- Handle sequence_id wraparound when reaching the upper limit

##### Consequences of Improper Implementation

Failing to properly implement sequence_id management can result in:
- Commands being ignored or rejected
- Incorrect error attribution
- Mixed up command responses
- Failed command acknowledgments
- Compromised safety features

##### Library Implementation

The library provides a `sequenceIdManager` utility that handles sequence IDs according to the protocol specification:

```typescript
import { getSequenceId, isValidSequenceId, START_SEQ_ID, END_SEQ_ID } from './utils/sequenceIdManager';

// Get next sequence ID (automatically handles wraparound)
const nextId = getSequenceId();  // Returns number between 20000-30000

// Validate incoming sequence IDs
const isValid = isValidSequenceId(nextId);  // Returns true if within valid range

// Example usage in commands
async function sendCommand(command: any) {
    // Automatically assign next valid sequence ID
    command.sequence_id = getSequenceId();
    
    // Send command and validate response sequence_id matches
    const response = await sendToDevice(command);
    if (!isValidSequenceId(response.sequence_id)) {
        throw new Error('Invalid sequence ID in response');
    }
    if (response.sequence_id !== command.sequence_id) {
        throw new Error('Response sequence ID mismatch');
    }
    
    return response;
}
```

The sequence ID management is handled automatically by the library's internal command processing, so you typically don't need to manage it directly when using the public API.

```typescript
import { BambuClient, Fan, UpdateFanCommand } from "bambu-node"

// define a printer connection
const client = new BambuClient({
	host: "your_printers_ip",
	accessToken: "your_printers_access_token",
	serialNumber: "your_printers_sn",
})

// more about the available events below
client.on("message", (topic, key, data) => {
	console.log(`New ${key} message!`, data)
})

client.on("printer:statusUpdate", (oldStatus, newStatus) => {
	console.log(`The printer's status has changed from ${oldStatus} to ${newStatus}!`)
})

// connect to the printer
await client.connect()

// update the speed of the auxiliary fan to 100%
await client.executeCommand(new UpdateFanCommand({ fan: Fan.AUXILIARY_FAN, speed: 100 }))

// we don't want to do anything else => we close the connection
// (can be kept open indefinitely if needed)
await client.disconnect()
```

## API

#### Legend

<u>Unnamed things inside classes</u>: Other classes that extend that class.

Every method, command and response is documented in JSDoc, so only events & utility
classes are documented here.

- Bambu Node
  - [Class: `BambuClient`](#class-bambuclient)
    - Method: `BambuClient.connect()`
    - Method: `BambuClient.disconnect()`
    - Method: `BambuClient.subscribe(topic)`
    - Method: `BambuClient.executeCommand(command)`
    - [Event: `message`](#message)
    - [Event: `rawMessage`](#rawmessage)
    - [Event: `client:connect`](#clientconnect)
    - [Event: `client:disconnect`](#clientdisconnect)
    - [Event: `printer:dataUpdate`](#printerdataupdate)
    - [Event: `printer:statusUpdate`](#printerstatusupdate)
    - [Event: `job:update`](#jobupdate)
    - [Event: `job:start`](#jobstart)
    - [Event: `job:pause`](#jobpause)
    - [Event: `job:offlineRecovery`](#jobofflineRecovery)
    - [Event: `job:unpause`](#jobunpause)
    - [Event: `job:finish`](#jobfinish)
  - [Class: `Job`](#class-job)
    - Method: `Job.update(data)`
    - Getter: `Job.data`
  - Class: `AbstractCommand`
    - Class: `GCodeCommand`
      - `GCodeFileCommand`
      - `GCodeLineCommand`
    - `GetVersionCommand`
    - `PushAllCommand`
    - `UpdateFanCommand`
    - `UpdateLightCommand`
    - `UpdateSpeedCommand`
    - `UpdateStateCommand`
    - `UpdateTempCommand`
  - _Command Responses_
    - info
      - Class: `InfoMessageCommand`
        - `GetVersionResponse`
    - mcPrint
      - Class: `McPrintMessageCommand`
        - `PushInfoResponse`
    - print
      - Class: `PrintMessageCommand`
        - `GCodeFileResponse`
        - `GCodeLineResponse`
        - `ProjectFileResponse`
        - `PushAllResponse`
          - `PushStatusResponse`
        - `UpdateFanResponse`
        - `UpdateLightResponse`
        - `UpdateSpeedResponse`
        - `UpdateStateResponse`
        - `UpdateTempResponse`

### Class: BambuClient

Responsible for managing the connection and messages to/from the printer.

#### Events

##### `rawMessage`

Triggered whenever a new message is received from the MQTT broker.

#### `message`

Triggered whenever a new <u>known</u> message is received from the MQTT broker. It's
already parsed and sent using its type.

#### `client:connect`

Triggered whenever the client connects to the printer. This will also trigger on a
reconnect.

#### `client:disconnect`

Triggered whenever the client disconnects from the printer. This can be on purpose using
the `client#disconnect` method or when the printer itself goes offline.

#### `client:error`

Triggered whenever the internal MQTT client encounters an error.

Examples include:

- Unresolvable host provided
- Incorrect credentials provided
- Unexpected responses

#### `printer:dataUpdate`

Triggered whenever new data is received from the printer and is merged into the data class
field.

#### `printer:statusUpdate`

Triggered whenever the printer's status changes to a new status.

#### `job:update`

Triggered whenever the current Job's data gets updated.

#### `job:start`

Triggered whenever a new printing job starts.

#### `job:pause`

Triggered whenever the current print job is paused.

#### `job:offlineRecovery`

Triggered whenever the current print job was recovered after the printer came back online
from an offline state.

#### `job:unpause`

Triggered whenever the current print job is resumed.

#### `job:finish`

Triggered whenever the current print job finishes.

Possible reasons:

- `SUCCESS`: Triggered whenever the current print job finishes without errors.
- `FAILED`: Triggered whenever the current print job finishes without errors.
- `UNEXPECTED`: Triggered whenever the current print job finishes unexpectedly. This is
  only included as a proof of concept and is 99% bound to never happen.

### Class: Job

Responsible for managing the data about the current print job. It collects historical
data, error codes, etc. It is included in every event starting with `job:`.

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what
you would like to change.

## License

[MIT © Márk Böszörményi, Aaron Scherer](LICENSE)
