Skip to content
This repository has been archived by the owner on Sep 21, 2023. It is now read-only.

Commit

Permalink
Support Last Will and Testament.
Browse files Browse the repository at this point in the history
  • Loading branch information
rovale committed Apr 27, 2016
1 parent b34a14f commit ad7b86a
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -20,12 +20,13 @@ An MQTT client for [Espruino](http://www.espruino.com/).
- [x] Handle receiving multiple Publish packets at once.
- [x] Respond with PubAck packet when receiving a QoS 1 Publish packet.
- [x] Support sending retained Publish packets.
- [ ] Implement Last Will and Testament.
- [x] Support Last Will and Testament.
- [ ] Handle not receiving PingResp packets.
- [ ] Reconnect on lost connection.
- [ ] Optimize memory usage and size.
- [ ] Handle not being able to establish connection to host.
- [ ] Handle receiving long Publish packets.
- [ ] Create tests for example

## Technial notes
- About the [npm-scripts](https://docs.npmjs.com/misc/scripts)
Expand Down
21 changes: 15 additions & 6 deletions espruino/projects/example.js
@@ -1,17 +1,26 @@
var MicroMqttClient = require("micro-mqtt").MicroMqttClient;

var mqttClient = new MicroMqttClient({ host: "192.168.2.4", username: "username", password: "password" });
var mqttClient = new MicroMqttClient({
host: "192.168.2.4",
clientId: "espruino",
username: "username", password: "password",
will: {
topic: "rovale/espruino/status",
message: "offline",
qos: 1,
retain: true
}
});

mqttClient.on('connected', function () {
mqttClient.subscribe("rovale/#");
mqttClient.subscribe("rovale/#", 1);
mqttClient.publish("rovale/espruino/status", "online", 1, true);
});

/*
mqttClient.on('publish', function (pub) {
console.log("on publish");
console.log(pub);
console.log("on publish");
console.log(pub);
});
*/

mqttClient.on('debug', function (debug) {
console.log("[debug] " + debug);
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "micro-mqtt",
"version": "0.0.15",
"version": "0.0.16",
"description": "An MQTT client for Espruino.",
"main": "./modules/micro-mqtt.ts",
"scripts": {
Expand Down
8 changes: 8 additions & 0 deletions src/module/ConnectionOptions.ts
Expand Up @@ -7,6 +7,14 @@ interface ConnectionOptions {
username?: string;
password?: string;
clientId?: string;
will?: ConnectionOptionsWill;
}

interface ConnectionOptionsWill {
topic: string;
message: string;
qos?: number;
retain?: boolean;
}

export default ConnectionOptions;
24 changes: 21 additions & 3 deletions src/module/micro-mqtt.ts
Expand Up @@ -53,7 +53,7 @@ export interface Network {
* The MQTT client.
*/
export class MicroMqttClient {
public version = '0.0.15';
public version = '0.0.16';

private options: ConnectionOptions;
private network: Network;
Expand All @@ -70,6 +70,11 @@ export class MicroMqttClient {
options.port = options.port || defaultPort;
options.clientId = options.clientId || '';

if (options.will) {
options.will.qos = options.will.qos || defaultQos;
options.will.retain = options.will.retain || false;
}

this.options = options;
this.network = network;
}
Expand Down Expand Up @@ -214,11 +219,18 @@ export module MqttProtocol {
* Connect flags
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349229
*/
function createConnectionFlags(options: ConnectionOptions) {
function createConnectFlags(options: ConnectionOptions) {
let flags = 0;
flags |= (options.username) ? ConnectFlags.UserName : 0;
flags |= (options.username && options.password) ? ConnectFlags.Password : 0;
flags |= ConnectFlags.CleanSession;

if (options.will) {
flags |= ConnectFlags.Will;
flags |= options.will.qos << 3;
flags |= (options.will.retain) ? ConnectFlags.WillRetain : 0;
}

return flags;
}

Expand Down Expand Up @@ -265,11 +277,17 @@ export module MqttProtocol {

const protocolName = createString('MQTT');
const protocolLevel = String.fromCharCode(4);
const flags = String.fromCharCode(createConnectionFlags(options));
const flags = String.fromCharCode(createConnectFlags(options));

const keepAlive: string = String.fromCharCode(...keepAliveBytes());

let payload = createString(options.clientId);

if (options.will) {
payload += createString(options.will.topic);
payload += createString(options.will.message);
}

if (options.username) {
payload += createString(options.username);
if (options.password) {
Expand Down
10 changes: 7 additions & 3 deletions src/test/MicroMqttClientTest.ts
Expand Up @@ -59,7 +59,11 @@ describe('MicroMqttClient', () => {
host: 'host',
clientId: 'some-client',
username: 'some-username',
password: 'some-password'
password: 'some-password',
will: {
topic: 'some/willTopic',
message: 'offline'
}
}, network);

networkSocket = new TestNetworkSocket();
Expand All @@ -70,8 +74,8 @@ describe('MicroMqttClient', () => {
it('it should send a Connect packet.', () => {
networkSocket.sentPackages.should.have.lengthOf(1);
const packet = new ConnectPacketVerifier(networkSocket.sentPackages[0]);
packet.shouldHaveConnectFlags(ConnectFlags.UserName | ConnectFlags.Password | ConnectFlags.CleanSession);
packet.shouldHavePayload('some-client', 'some-username', 'some-password');
packet.shouldHaveConnectFlags(ConnectFlags.UserName | ConnectFlags.Password | ConnectFlags.CleanSession | ConnectFlags.Will);
packet.shouldHavePayload('some-client', 'some/willTopic', 'offline', 'some-username', 'some-password');
});
});

Expand Down
98 changes: 97 additions & 1 deletion src/test/MqttProtocolTest.ts
Expand Up @@ -87,7 +87,8 @@ describe('MqttProtocol', () => {
beforeEach(() => {
packet = new ConnectPacketVerifier(MqttProtocol.createConnectPacket({
host: 'host',
clientId: 'some-client', username: 'some-username',
clientId: 'some-client',
username: 'some-username',
password: 'some-password'
}));
});
Expand All @@ -100,6 +101,101 @@ describe('MqttProtocol', () => {
packet.shouldHavePayload('some-client', 'some-username', 'some-password');
});
});

context('specifying the Last Will Testament', () => {
context('With QoS 0, not retained', () => {
beforeEach(() => {
packet = new ConnectPacketVerifier(MqttProtocol.createConnectPacket({
host: 'host',
clientId: 'some-client',
username: 'some-username',
will: {
topic: 'some/willTopic',
message: 'offline',
qos: 0,
retain: false
}
}));
});

it('it should provide the correct connect flags.', () => {
packet.shouldHaveConnectFlags(ConnectFlags.CleanSession | ConnectFlags.UserName | ConnectFlags.Will);
});

it('it should provide the will topic, and the will message.', () => {
packet.shouldHavePayload('some-client', 'some/willTopic', 'offline', 'some-username');
});
});

context('With QoS 1, not retained', () => {
beforeEach(() => {
packet = new ConnectPacketVerifier(MqttProtocol.createConnectPacket({
host: 'host',
clientId: 'some-client',
will: {
topic: 'some/willTopic',
message: 'offline',
qos: 1,
retain: false
}
}));
});

it('it should provide the correct connect flags.', () => {
packet.shouldHaveConnectFlags(ConnectFlags.CleanSession | ConnectFlags.Will | ConnectFlags.WillQoS1);
});

it('it should provide the will topic, and will message.', () => {
packet.shouldHavePayload('some-client', 'some/willTopic', 'offline');
});
});

context('With QoS 2, not retained', () => {
beforeEach(() => {
packet = new ConnectPacketVerifier(MqttProtocol.createConnectPacket({
host: 'host',
clientId: 'some-client',
will: {
topic: 'some/willTopic',
message: 'offline',
qos: 2,
retain: false
}
}));
});

it('it should provide the correct connect flags.', () => {
packet.shouldHaveConnectFlags(ConnectFlags.CleanSession | ConnectFlags.Will | ConnectFlags.WillQoS2);
});

it('it should provide the will topic, and will message.', () => {
packet.shouldHavePayload('some-client', 'some/willTopic', 'offline');
});
});

context('With QoS 0, retained', () => {
beforeEach(() => {
packet = new ConnectPacketVerifier(MqttProtocol.createConnectPacket({
host: 'host',
clientId: 'some-client',
will: {
topic: 'some/willTopic',
message: 'offline',
qos: 0,
retain: true
}
}));
});

it('it should provide the correct connect flags.', () => {
packet.shouldHaveConnectFlags(ConnectFlags.CleanSession | ConnectFlags.Will | ConnectFlags.WillRetain);
});

it('it should provide the will topic, and will message.', () => {
packet.shouldHavePayload('some-client', 'some/willTopic', 'offline');
});
});
});
});

context('When creating a Publish packet', () => {
Expand Down

0 comments on commit ad7b86a

Please sign in to comment.