Skip to content

Commit

Permalink
working example for setting color, brightness and receiving color,
Browse files Browse the repository at this point in the history
brightness and name
  • Loading branch information
Marmelatze committed Jan 24, 2016
1 parent 42ae4e7 commit bd90be5
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 112 deletions.
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2016 Florian Pfitzer

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
70 changes: 70 additions & 0 deletions README.md
@@ -0,0 +1,70 @@
# Avea Bulb


## Binary Protocol

Communication with a bulb is done via the service `f815e810456c6761746f4d756e696368` and its characteristic `f815e811456c6761746f4d756e696368`.
The first byte in the protocol is the command, the others are the payload. When sending a command without payload the current value from this command is send back as notification.

### Available Commands

* 0x35: Color
* 0x58: User defined name of the bulb
* 0x57: Brightness

### Color

The color is defined by these four variables: white, red, green, blue. Each variable has a value between 0 and 4095 (0xfff).

The byte sequence, which has to be send to bulb can be computed on the following way:

For example we set the color to `w: 0xaaa, r: 0xbbb, g:0xccc, b:0xddd`, with a fading of `0x111` we send:


| `35` | `11 01`| `0a 00`| `aa 8a`| `bb 3b`| `cc 2c`| `dd 1d`|
|----------|-----------|-----------|-----------|-----------|-----------|-----------|
|Color command | fading in LE | unkown | white with 8 as prefix | red with 3 as prefix | green with 2 as prefix | blue with 1 as prefix |

To compute the color part the following js function is used:

```javascript
const buffer = new Buffer(8);
buffer.writeUInt16LE(this.white | 0x8000, 0);
buffer.writeUInt16LE(this.red | 0x3000, 2);
buffer.writeUInt16LE(this.green | 0x2000, 4);
buffer.writeUInt16LE(this.blue | 0x1000, 6);
```

The color send back through the notification has a different format. For the same values as set above the following bytes are received:

| `35` | `da 0a` | `5e 1d` | `57 2c` | `4f 3b` | `aa 0a` | `dd 1d` | `cc 2c`| `bb 3b`|
|-------|----------|----------|---------|---------|---------|---------|--------|--------|
| Color command | current white (`0xada`) | current blue (`0xd5e`) | current green (`0xc57`) | current red (`0xb4f`) | target white (`0xaaa`) | target blue (`0xddd`) | target green (`0xccc`) | target red (`0xbbb`) |


```javascript
// command code byte already stripped

// these are the current colors of the lamp.
let white = buffer.readUInt16LE(0);
let blue = buffer.readUInt16LE(2) ^ 0x1000;
let green = buffer.readUInt16LE(4) ^ 0x2000;
let red = buffer.readUInt16LE(6) ^ 0x3000;

// these are the colors the lamp is fading to.
let whiteTarget = buffer.readUInt16LE(8);
let blueTarget = buffer.readUInt16LE(10) ^ 0x1000;
let greenTarget = buffer.readUInt16LE(12) ^ 0x2000;
let redTarget = buffer.readUInt16LE(14) ^ 0x3000;
```


### Name

The name is received as a null terminated string. For example: `0x58 0x45 0x6c 0x67 0x61 0x74 0x6f 0x20 0x41 0x76 0x65 0x61 0x00` = Elgato Avea

### Brightness

Brightness goes from 0-4095 and is send and received as little endian value.

e.g. `0x57ff0f` for 4095.
153 changes: 130 additions & 23 deletions lib/avea.js
@@ -1,27 +1,44 @@
"use strict";

let COLOR_SERVICE_ID = "f815e810456c6761746f4d756e696368";
let COLOR_CHARACTERISTIC_ID = "f815e811456c6761746f4d756e696368";
const Queue = require("promise-queue");
const Color = require("./color");
const winston = require("winston");

let SERVICE_ID = "f815e810456c6761746f4d756e696368";
let CHARACTERISTIC_ID = "f815e811456c6761746f4d756e696368";

class Avea {
constructor(peripheral) {
this.peripheral = peripheral;
this.connected = false;
this.colorCharacteristic = null;

this.characteristic = null;
this.running = false;
this.commandQueue = [];
winston.info("new bulb found", {bulb: this.id()});

peripheral.on('connect', () => { this.connected = true; });
peripheral.on('diconnect', () => { console.log("disconnected"); this.connected = false; });
peripheral.on("connect", () => {
this.connected = true;
winston.info("connected", {bulb: this.id()});
});
peripheral.on("diconnect", () => {
this.connected = false;
winston.info("connected", {bulb: this.id()});
});
}

id() {
return this.peripheral.id;
}

connect() {
return new Promise((resolve, reject) => {
if (this.connected && this.peripheral.state === "connected") {
resolve();
return;
}

this.peripheral.connect(function(err) {
winston.info("connecting", {bulb: this.id()});
this.peripheral.connect((err) => {
this.characteristic = null;
if (err) {
reject(err);
} else {
Expand All @@ -31,34 +48,124 @@ class Avea {
});
}

_getColorCharacteristic() {
_getCharacteristic() {
return this.connect().then(() => {
return new Promise((resolve, reject) => {
if (null !== this.colorCharacteristic) {
resolve(this.colorCharacteristic);
if (null !== this.characteristic) {
resolve(this.characteristic);
} else {
this.peripheral.discoverSomeServicesAndCharacteristics([COLOR_SERVICE_ID], [COLOR_CHARACTERISTIC_ID], (err, services, characteristics) => {
this.peripheral.discoverSomeServicesAndCharacteristics([SERVICE_ID], [CHARACTERISTIC_ID], (err, services, characteristics) => {
if (err) {
reject(err);
} else {
this.colorCharacteristic = characteristics[0];
resolve(this.colorCharacteristic);
return reject(err);
}

this.characteristic = characteristics[0];

this.characteristic.notify(true, (err) => {
resolve(characteristics[0]);
});
});
}
});
});
}

setColor(color) {
this._getColorCharacteristic().then((characteristic) => {
console.log(color, this.peripheral.state);
color = color.replace(/:/g, "");
let buffer = new Buffer(color, "hex");

characteristic.write(buffer, true, function(error) {
getName() {
return new Promise((resolve, reject) => {
this._write(new Buffer([0x58])).then((response) => {
resolve(response.toString("utf8"));
});
});
}

getColor() {
return new Promise((resolve, reject) => {
this._write(new Buffer([0x35])).then((response) => {
resolve(Color.fromResponse(response.slice(3, response.length)));
});
})
}

setColor(color, delay) {
delay = delay || 100;
const header = new Buffer([
0x35,
0x00,
0x00,
10,
0,
]);
header.writeUInt16LE(delay, 1);

const buffer = Buffer.concat([header, color.toBuffer()]);
winston.info("setColor", {bulb: this.id(), color: color, delay: delay});
this._write(new Buffer(buffer)).then((response) => {
});
}

getBrightness() {
return new Promise((resolve, reject) => {
this._write(new Buffer([0x57])).then((response) => {
resolve(Math.floor(response.readInt16LE() / 16));
});
});
}

setBrightness(brighness) {
const buffer = new Buffer([
0x57, 0x00, 0x00,
]);
buffer.writeInt16LE(((brighness + 1) * 16) - 1, 1);
winston.info("setBrighness", {bulb: this.id(), brightness: brighness});
this._write(new Buffer(buffer)).then((response) => {
});
}


_write(buffer) {
return new Promise((resolve, reject) => {
this.commandQueue.push([buffer, resolve, reject]);
this._run();
})
}

_run() {
if (this.running) {
return;
}
if (this.commandQueue.length == 0) {
return;
}

this.running = true;
this._getCharacteristic().then((characteristic) => {
let queue = new Queue(1, Infinity);
while(this.commandQueue.length > 0) {
let command = this.commandQueue.shift();
queue.add(() => { return this._writeAndWaitForResponse(characteristic, command[0]); })
.then((data) => {
command[1](data);
});
}
this.running = false;
});
}

_writeAndWaitForResponse(characteristic, buffer) {
return new Promise((resolve, reject) => {
let listener = (data, isNotification) => {
if (data[0] === buffer[0]) {
characteristic.removeListener("data", listener);
winston.info("received", {bulb: this.id(), data: data.toString("hex")});
resolve(data.slice(1, data.length));
}
};
characteristic.on("data", listener);
winston.info("write", {bulb: this.id(), data: buffer.toString("hex")});
characteristic.write(buffer, true, (error) => {
if (error) {
throw error;
reject(error);
}
});
});
Expand Down
58 changes: 58 additions & 0 deletions lib/color.js
@@ -0,0 +1,58 @@
"use strict";

class Color {
/**
* each parameter goes from 0-4095 (0xfff)
*
* @param white
* @param red
* @param green
* @param blue
*/
constructor(white, red, green, blue) {
this.white = white;
this.red = red;
this.green = green;
this.blue = blue;
}

toString() {
return "{white: " + this.white + ", red: " + this.red + ", green: " + this.green + ", blue: " + this.blue + "}";
}

toBuffer() {
let color = new Buffer(8);
color.writeUInt16BE(this.white, 0);
color.writeUInt16BE(this.red, 2);
color.writeUInt16BE(this.green, 4);
color.writeUInt16BE(this.blue, 6);

const buffer = new Buffer(8);
buffer.writeUInt16LE(this.white | 0x8000, 0);
buffer.writeUInt16LE(this.red | 0x3000, 2);
buffer.writeUInt16LE(this.green | 0x2000, 4);
buffer.writeUInt16LE(this.blue | 0x1000, 6);

return buffer;
}

static fromResponse(buffer) {
// these are the current colors of the lamp.
let white = buffer.readUInt16LE(0);
let blue = buffer.readUInt16LE(2) ^ 0x1000;
let green = buffer.readUInt16LE(4) ^ 0x2000;
let red = buffer.readUInt16LE(6) ^ 0x3000;

// these are the colors the lamp is fading to.
let whiteTarget = buffer.readUInt16LE(8);
let blueTarget = buffer.readUInt16LE(10) ^ 0x1000;
let greenTarget = buffer.readUInt16LE(12) ^ 0x2000;
let redTarget = buffer.readUInt16LE(14) ^ 0x3000;

return {
current: new Color(white, red, green, blue),
target: new Color(whiteTarget, redTarget, greenTarget, blueTarget)
};
}
}
module.exports = Color;

0 comments on commit bd90be5

Please sign in to comment.