Skip to content

Commit

Permalink
Merge pull request #42 from Grizzelbee/Dev_0.7.0
Browse files Browse the repository at this point in the history
Dev 0.7.0
  • Loading branch information
Grizzelbee authored Jan 8, 2021
2 parents 01e0893 + c68670f commit 61c0df7
Show file tree
Hide file tree
Showing 12 changed files with 1,675 additions and 1,203 deletions.
29 changes: 0 additions & 29 deletions admin/index_m.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,6 @@


// Create secrect for encrypted password storage
var secret;
function encrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function decrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}

function load(settings, onChange) {
if (!settings) return;
$('.value').each(function () {
Expand All @@ -57,16 +41,6 @@
});
}
});
socket.emit('getObject', 'system.config', function (err, obj) {
secret = (obj.native ? obj.native.secret : '') || '3eezLO2gNPrt1ww0pcWNhqPZxMjfb3br';
var $key = $('#Password');
settings['Password'] = decrypt(secret, settings['Password']);
$key.val(settings['Password']).change(function () {
onChange();
}).keyup(function () {
onChange();
});
});

onChange(false);
// (re)initialize all the Materialize labels on the page
Expand All @@ -83,9 +57,6 @@
obj[id] = $this.prop('checked');
} else {
let value = $this.val();
if ( id === 'Password') {
value = encrypt(secret, value);
}
obj[id] = value;
}
});
Expand Down
95 changes: 95 additions & 0 deletions dyson-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';

const _ = require('lodash');
const crypto = require('crypto');

// class DysonUtils {
// DysonUtils() {}
// }

/**
* Function zeroFill
*
* Formats a number as a string with leading zeros
*
* @param number {number} Value thats needs to be filled up with leading zeros
* @param width {number} width of the complete new string incl. number and zeros
*
* @returns The given number filled up with leading zeros to a given width (excluding the negative sign), returns empty string if number is not an actual number.
*/
module.exports.zeroFill = function (number, width) {
const num = parseInt(number);

if (isNaN(num)) {
return '';
}

const negativeSign = num < 0 ? '-' : '';
const str = '' + Math.abs(num);

return `${negativeSign}${_.padStart(str, width, '0')}`;
};

/**
* checkAdapterConfig
*
* {promise} Tests whether the given adapters config is valid
* resolves if the config is valid
* rejects if the config is invalid
*
* @param config {Adapter} ioBroker adapter which contains the configuration that should be checked
*/
module.exports.checkAdapterConfig = async function (adapter) {
adapter.log.debug('Entering function [checkAdapterConfig]...');

const config = adapter.config;

// Masking sensitive fields (password) for logging configuration (creating a deep copy of the config)
const logConfig = JSON.parse(JSON.stringify(config));
logConfig.Password = '(***)';
// TODO Move to separate function for masking config wherever needed in this module

return new Promise(
function (resolve, reject) {
// TODO Do more precise tests. This is very rough
if ((!config.email || config.email === '')
|| (!config.Password || config.Password === '')
|| (!config.country || config.country === '')) {
adapter.log.debug(`Invalid configuration provided: ${logConfig}`);
reject('Given adapter config is invalid. Please fix.');
} else {
resolve('Given config seems to be valid.');
}
});
};

/**
* Function decryptMqttPasswd
* decrypts the fans local mqtt password and returns a value you can connect with
*
* @param LocalCredentials {string} encrypted mqtt password
*/
module.exports.decryptMqttPasswd = function (LocalCredentials) {
// Gets the MQTT credentials from the thisDevice (see https://github.com/CharlesBlonde/libpurecoollink/blob/master/libpurecoollink/utils.py)
const key = Uint8Array.from(Array(32), (_, index) => index + 1);
const initializationVector = new Uint8Array(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, initializationVector);
const decryptedPasswordString = decipher.update(LocalCredentials, 'base64', 'utf8') + decipher.final('utf8');
const decryptedPasswordJson = JSON.parse(decryptedPasswordString);
return decryptedPasswordJson.apPasswordHash;
};

/**
* Parse an incoming JSON message payload from the Dyson device
*
* @param msg Incoming JSON message
*/
module.exports.parseDysonMessage = function (msg) {
if (null == msg || '' == msg) return;

// TODO incomplete

// const data = JSON.parse(msg);
// console.log(data);
return;
};
116 changes: 116 additions & 0 deletions dyson-utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use strict';

const fs = require('fs');

const { fail } = require('assert');
const { expect } = require('chai');
const sinon = require('sinon');

const dysonUtils = require('./dyson-utils');

describe('dysonUtils => zeroFill', () => {
it('should left-pad zeroes for positive input numbers', () => {
const data = [
[5, 4, '0005'],
[1, 1, '1'],
[45, 2, '45'],
[734, 8, '00000734'],
];

data.forEach(d => {
expect(dysonUtils.zeroFill(d[0], d[1])).to.equal(d[2]);
});
});

it('should left-pad zeroes for negative input numbers (width excludes negative sign)', () => {
const data = [
[-5, 4, '-0005'],
[-1, 1, '-1'],
[-45, 2, '-45'],
[-734, 8, '-00000734'],
];

data.forEach(d => {
expect(dysonUtils.zeroFill(d[0], d[1])).to.equal(d[2]);
});
});

it('should return empty string for invalid (non-number or empty) input', () => {
expect(dysonUtils.zeroFill('', 50)).to.equal('');
expect(dysonUtils.zeroFill('äöüß', 50)).to.equal('');
});
});

describe('dysonUtils => checkAdapterConfig', () => {
after(() => sinon.restore() );

const fakeAdapter = {
log: { debug: sinon.fake() },
config: null
};

it('should reject an empty adapter configuration', () => {
fakeAdapter.config = {
temperatureUnit: '',
pollInterval: '',
country: '',
email: '',
Password: ''
};
expect(dysonUtils.checkAdapterConfig(fakeAdapter)).to.be.rejected;
});

it('should pass with a valid adapter configuration', () => {
fakeAdapter.config = {
temperatureUnit: 'C',
pollInterval: 60,
country: 'DE',
email: '[email protected]',
Password: 'SecretPassword'
};
expect(dysonUtils.checkAdapterConfig(fakeAdapter)).to.be.fulfilled;
});

});

describe('dysonUtils => decrypt', () => {
it.skip('should verify decrypt mechanism', () => {});
});

describe('dysonUtils => decryptMqttPasswd', () => {
it.skip('should verify decrypt MQTT password mechanism', () => {});
});

describe('dysonUtils => parseDysonMsgPayload', () => {
// TODO See adapter.processMsg() for now, considering migration to separate message parser later

it('should ignore empty or null message payload', () => {
try {
dysonUtils.parseDysonMessage('');
dysonUtils.parseDysonMessage(null);
} catch (error) {
fail(`Error ${error} thrown during message processing.`);
}
});

it.skip('should parse a DP01 CURRENT-STATE payload', () => {
const msg = fs.readFileSync('./test/sample-data/sample-msg-DP01-1.json');
const data = JSON.parse(msg);

console.log(data);
});

it.skip('should parse a DP01 ENVIRONMENTAL-CURRENT-SENSOR-DATA payload', () => {
const msg = fs.readFileSync('./test/sample-data/sample-msg-DP01-2.json');
const data = JSON.parse(msg);

console.log(data);
});

it.skip('should parse a DP01 STATE-CHANGE payload', () => {
const msg = fs.readFileSync('./test/sample-data/sample-msg-DP01-3.json');
const data = JSON.parse(msg);

console.log(data);
});
});
22 changes: 18 additions & 4 deletions io-package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
{
"common": {
"name": "dysonairpurifier",
"version": "0.6.0",
"version": "0.7.0",
"news": {
"0.7.0": {
"en": "Breaking-change - Many bugfixes and improvements - Refer to changelog for details.",
"de": "Breaking-Change - Viele Bugfixes und Verbesserungen - Weitere Informationen finden Sie im Changelog.",
"ru": "Критические изменения - Многие исправления и улучшения - Подробности см. В журнале изменений.",
"pt": "Breaking-change - Muitas correções de bugs e melhorias - Consulte o changelog para detalhes.",
"nl": "Breaking-change - Veel bugfixes en verbeteringen - Raadpleeg het changelog voor details.",
"fr": "Breaking-change - De nombreuses corrections de bogues et améliorations - Reportez-vous au journal des modifications pour plus de détails.",
"it": "Cambiamento decisivo - Molte correzioni di bug e miglioramenti - Fare riferimento al registro delle modifiche per i dettagli.",
"es": "Cambio rotundo: muchas correcciones de errores y mejoras: consulte el registro de cambios para obtener más detalles.",
"pl": "Przełomowa zmiana - Wiele poprawek i ulepszeń - Szczegółowe informacje można znaleźć w dzienniku zmian.",
"zh-cn": "重大更改-许多错误修正和改进-有关详细信息,请参阅更改日志。"
},
"0.6.0": {
"en": "minor bugfix and small improvements.",
"de": "kleiner Bugfix und kleine Verbesserungen.",
Expand Down Expand Up @@ -150,23 +162,25 @@
"type": "climate-control",
"compact": true,
"materialize": true,
"materializeTab": true,
"supportCustoms": false,
"connectionType": "local",
"dataSource": "pull",
"dependencies": [
{
"js-controller": ">=1.4.2"
"js-controller": ">=3.0.0",
"admin": ">=4.0.9"
}
]
},
"native": {
"email": "",
"Password": "",
"country": "",
"pollInterval": 30,
"temperatureUnit": "C"
},
"encryptedNative": {
"Password": ""
},
"objects": [],
"instanceObjects": []
}
Loading

0 comments on commit 61c0df7

Please sign in to comment.