Skip to content

Commit

Permalink
🎉 initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bonan committed Aug 15, 2017
0 parents commit 5a312b0
Show file tree
Hide file tree
Showing 13 changed files with 702 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.idea/
dist/
lib/
ui/
*.log
*.pid
node_modules/
.npm
.eslintcache
*.tgz
.env
package-lock.json
12 changes: 12 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.idea/
dist/
ui/
*.log
*.pid
node_modules/
.npm
.npmignore
.gitignore
.eslintcache
*.tgz
.env
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Hemtjänst javascript library

A node and browser compatible library for Hemtjänst written in TypeScript.

This project is alpha-quality and there will be breaking changes in future releases.
1 change: 1 addition & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('lib/main');
51 changes: 51 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "hemtjanst",
"version": "0.0.2",
"description": "Hemtjänst library",
"author": {
"name": "bonan",
"email": "npm@bonan.se"
},
"bugs": {
"url": "https://github.com/hemtjanst/hemtjanst-js/issues"
},
"contributors": [
{
"name": "bonan",
"email": "npm@bonan.se"
}
],
"dependencies": {
},
"devDependencies": {
"debug": "^3.0.0",
"mqtt": "^2.11.0",
"tslint": "^5.6.0",
"typescript": "^2.4.2",
"webpack": "^3.5.4",
"uglify-js": "^3.0.27",
"awesome-typescript-loader": "^3.2.2"
},
"homepage": "https://github.com/hemtjanst/hemtjanst-js",
"keywords": [
"tradfri",
"mqtt",
"hemtjanst"
],
"license": "Apache-2.0",
"browser": "main.js",
"main": "main.js",
"scripts": {
"test": "node lib/test.js",
"build": "npm run compile; npm run webpack; npm run uglify",
"compile": "node node_modules/typescript/bin/tsc",
"webpack": "node node_modules/webpack/bin/webpack.js lib/main.js dist/hemtjanst.js --output-library hemtjanst",
"uglify": "node node_modules/uglify-js/bin/uglifyjs -o dist/hemtjanst.min.js dist/hemtjanst.js",
"combine": "npm run compile; npm run webpack; node node_modules/webpack/bin/webpack.js node_modules/mqtt dist/mqtt.js --output-library mqtt; cat dist/hemtjanst.js dist/mqtt.js > dist/hemtjanst-mqtt.js; rm dist/mqtt.js; node node_modules/uglify-js/bin/uglifyjs -o dist/hemtjanst-mqtt.min.js dist/hemtjanst-mqtt.js"
},
"readmeFilename": "README.md",
"repository": {
"type": "git",
"url": "https://github.com/hemtjanst/hemtjanst-js.git"
}
}
236 changes: 236 additions & 0 deletions src/device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import {Feature, FeatureMeta, FeatureType} from "./feature";
import {utils} from "./utils";
import {Manager} from "./manager";
import {log, debug} from "./log";


export declare type ValueCallback = (device: Device, feature: string|FeatureType, value: string) => any

export declare interface DeviceMeta {
manufacturer?: string;
model?: string;
serialNumber?: string;
feature?: {[type: string]: FeatureMeta};
lastWillID?: string;
}

export class Device {

private manager: Manager;
private topic: string;
private name: string;
private type: DeviceType;
private meta: DeviceMeta;
private feature = {};

constructor(topic: string, name: string, type: DeviceType, opts: DeviceMeta) {
this.topic = topic;
this.name = name;
utils.typeName(type, DeviceType);
this.type = type;
if (opts && typeof opts === "object") {
this.meta = opts;
if (opts.feature && typeof opts.feature === "object") {
for (let k in opts.feature) {
this.addFeature(k, opts.feature[k]);
}
}
}
}

public addFeature(type: string|FeatureType, opts: FeatureMeta) {
let fName = utils.typeName(type, FeatureType);
if (fName === "") {
throw new Error("Invalid or unknown feature: "+type)
}
let feature = new Feature();
feature.opts = opts;
this.feature[fName] = feature;
}

public validate(): boolean {
if (!this.meta || typeof(this.meta) !== "object") {
throw new Error("Device meta not instantiated");
}
if (!this.topic) {
throw new Error("Device topic cannot be empty");
}
if (!this.name) {
throw new Error("Deivce name cannot be empty");
}
if (!this.type || !utils.typeName(this.type, DeviceType)) {
throw new Error("Invalid or missing device type");
}
if (!this.feature || typeof(this.feature) !== "object" || Object.keys(this.feature).length === 0) {
throw new Error("No features exists on device");
}
let res = true;
for (let k in this.feature) {
let fName = utils.typeName(k, FeatureType);
if (k !== fName) {
throw new Error("Invalid feature, "+k+" != "+fName)
}
}

return res;
}

public updateMeta() {
this.manager.publish(this.topic + "/meta", this.metaJson(), {qos: 1, retain: true});
}

public setManager(manager: Manager, isClient?: boolean) {
if (this.manager !== undefined) {
throw new Error("Manager already set");
}
this.manager = manager;
if (isClient) {
this.updateMeta();
}
for (let f in this.feature) {
let ftName = f;
let ft = this.feature[ftName];
let ftTopic = isClient ? this.setTopicName(ftName) : this.getTopicName(ftName);
manager.subscribe(ftTopic, (topic, payload, packet) => {
let list = isClient ? ft.setCallbacks : ft.getCallbacks;
if (list === undefined) {
return;
}
for (let i in list) {
list[i](this, ftName, payload.toString());
}
});
}
}

public topicName(): string {
return this.topic;
}

public metaJson(): string {
this.validate();
const o = {};
o["name"] = this.name;
o["type"] = utils.typeName(this.type, DeviceType);
o["manufacturer"] = this.meta.manufacturer;
o["model"] = this.meta.model;
o["serialNumber"] = this.meta.serialNumber;
o["lastWillID"] = this.meta.lastWillID;

let features = {};
for (let k in this.feature) {
let typeName = utils.typeName(k, FeatureType);
let ft = this.feature[k];
if (!ft.opts) {
ft.opts = {};
}
features[typeName] = {
min: ft.opts.min,
max: ft.opts.max,
step: ft.opts.step,
setTopic: ft.opts.setTopic,
getTopic: ft.opts.getTopic
};
}
o["feature"] = features;

return JSON.stringify(o)
}

public set(feature: string|FeatureType, value: string, callback?) {
if (!this.manager) {
throw new Error("Device has not manager, unable to Update()");
}
this.manager.publish(this.setTopicName(feature), value, {qos:1, retain:false, dup:false}, callback);
}

public update(feature: string|FeatureType, value: string, callback?) {
if (!this.manager) {
throw new Error("Device has not manager, unable to Update()");
}
this.manager.publish(this.getTopicName(feature), value, {qos:1, retain:true, dup:false}, callback);
}

public onSet(feature: string|FeatureType, cb: ValueCallback) {
this.getFeature(feature).onSet(cb);
}

public onUpdate(feature: string|FeatureType, cb: ValueCallback) {
this.getFeature(feature).onUpdate(cb);
}

public getFeatures() {
return this.feature;
}

public getFeature(feature: string|FeatureType): Feature {
let fName = utils.typeName(feature, FeatureType);
if (fName !== "" && this.feature[fName] !== undefined) {
return this.feature[fName];
}
throw new Error("Feature "+feature+" is not implemented on device "+this.topic);

}

public setTopicName(feature: string|FeatureType): string {
let t = this.topic + "/" + utils.typeName(feature, FeatureType) + "/set";
let f = this.getFeature(feature);
if (f.opts.setTopic) {
t = f.opts.setTopic;
}
return t;
}
public getTopicName(feature: string|FeatureType): string {
let t = this.topic + "/" + utils.typeName(feature, FeatureType) + "/get";
let f = this.getFeature(feature);
if (f.opts.getTopic) {
t = f.opts.getTopic;
}
return t;
}
}

export enum DeviceType {
AccessoryInformation = "3E",
AirPurifier = "BB",
AirQualitySensor = "8D",
BatteryService = "96",
BridgeConfiguration = "A1",
BridgingState = "62",
CameraControl = "111",
CameraRTPStreamManagement = "110",
CarbonDioxideSensor = "97",
CarbonMonoxideSensor = "7F",
ContactSensor = "80",
Door = "81",
Doorbell = "121",
Fan = "40",
FanV2 = "B7",
FilterMaintenance = "BA",
GarageDoorOpener = "41",
HeaterCooler = "BC",
HumidifierDehumidifier = "BD",
HumiditySensor = "82",
LeakSensor = "83",
LightSensor = "84",
Lightbulb = "43",
LockManagement = "44",
LockMechanism = "45",
Microphone = "112",
MotionSensor = "85",
OccupancySensor = "86",
Outlet = "47",
SecuritySystem = "7E",
Slat = "B9",
SmokeSensor = "87",
Speaker = "113",
StatefulProgrammableSwitch = "88",
StatelessProgrammableSwitch = "89",
Switch = "49",
TemperatureSensor = "8A",
Thermostat = "4A",
TimeInformation = "99",
TunneledBTLEAccessoryService = "56",
Window = "8B",
WindowCovering = "8C",
}
Loading

0 comments on commit 5a312b0

Please sign in to comment.