Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eq3device/eq3interface: #12

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

btsimonh
Copy link

@btsimonh btsimonh commented Nov 3, 2018

Hi Alikh31,

I've been working a lot with these devices now, and returned to noble (trying to make it work better!).

One of the things I've done is some heavy updates on the two lib files; in theory these mods don't change anything related to the node-red node, but I've NOT tested with the actual node. (all testing in function nodes so far).
If you are able to test in your setup, that would be great.

Details:
Make ALL sends parse the return.
Enhance return parsing.
add functions for schedules.
fix getInfo to setDate (it was screwing with my date/time on devices? - using the last time sent to it!).
fix various '0' fill left issues.
Add CC-RT-M-BLE.
return {error:} on error rather than nothing.
enhanced ecoMode ('holiday mode') setting with temp & date options.

I AM successfully using multiple devices with noble now.
So my next task is to review the node functionality with regard to what I want to achieve, and see if I can enhance it without disturbing existing users :). If I can, I'll provide another pull request.....

…rn parsing. add functions for schedules. fix getInfo to setDate, fix various '0' fill left issues. Add CC-RT-M-BLE. return {error:<error>} on error rather than nothing. enhanced ecoMode ('holiday mode') setting with temp & date options
@btsimonh
Copy link
Author

btsimonh commented Nov 3, 2018

just did a VERY quick test, and it the NR node seems to work :)

@btsimonh
Copy link
Author

btsimonh commented Nov 3, 2018

I notice in the node that you are setting a global[config.eq3device] = device.
Do you use this externally (e.g. in a function node), or do you know if anyone else uses it?.
One of the things I see as a need is to connect only when needed; else I don't think the 'phone app can work with the TRVs. Also connection management is quite evil in noble. I'm thinking about a similar approach of using a global, but to track all devices we've seen. Another thing I do is serialise all commands to each device. Also, some aspects of BLE are machine wide (e.g. scanning), and need to be dealt with appropriately. Other BLE related nodes will interfere without a little thought going in.

sorry, for the essay, but just thinking about how to approach the next step.
Another thing I'd like to consider is if the EQ3 devices are access over a BLE to MQTT gateway (if I can successfully build one! - built but not yet blown to my ESP32....)

@alikh31
Copy link
Owner

alikh31 commented Nov 3, 2018

@btsimonh thanks for your contribution. unfortunately I have no eq3 device in hand at the moment. if you are sure the code will fully function I am more than happy to merge it. would be great to test it further and make sure everything is in place.

If there are any changes to the api please update the readme file as well.

@btsimonh
Copy link
Author

btsimonh commented Nov 3, 2018

ok, will do more testing. thankyou...

@alikh31
Copy link
Owner

alikh31 commented Nov 8, 2018

@btsimonh is this ready to merge?

@btsimonh
Copy link
Author

btsimonh commented Nov 8, 2018

hi @alikh31,
give me a couple more weeks. It's pretty much there for what it does, but I'm still working hard on BLE in general, and that may result in some more re-work.
s

Copy link

@thomasnordquist thomasnordquist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work!
It's really helpful that you added some documentation.

The comments are mostly small things and style related, I didn't find anything actually wrong =)

It's always a good idea to keep functions/methods small, and their scope to a specific task.

I could test your work so far if you would like, I mostly use the manual and boost functions and valvePosition, targetTemperature properties. And since I get new hardware this week, I would be able to test the pairing as well.
I have a bunch of these

break;

case 2:
switch(info[1] & 0xf){
Copy link

@thomasnordquist thomasnordquist Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each case block could simply invoke it's own function so you can get smaller functions with nicer names. Having a nice name you could even remove comments like // normal info.

case 1:
    return this->parsedSysinfo();
    break;
case 2: 
    return $this->parsedNormalInfo();
    break;

This becomes even more relevant when you have things like nested switch-case blocks (like a few lines below).

};
break;

case 0x21:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear what the meaning of 0x21 is. A comment would be nice here.

return new Buffer(`11${temp}${dur}`, 'hex')
const temp = ('0'+(2 * temperature).toString(16)).slice(-2);
const dur = ('0'+(minDuration / 5).toString(16)).slice(-2);
return new Buffer(`14${temp}${dur}`, 'hex')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change intentional? (the change from 11 to 14)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes :). seem to remember it was wrong...., some time agao now though.

const tempNight = (2 * night).toString(16)
const tempDay = (2 * day).toString(16)
const tempNight = ('0'+(2 * night).toString(16)).slice(-2);
const tempDay = ('0'+(2 * day).toString(16)).slice(-2);
Copy link

@thomasnordquist thomasnordquist Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding '0'+( ... ).slice(-2) correctly, this is supposed to be a "left-pad" function.

While it is a neat way to expand a number to two digits, it is not very readable.
It would be nicer to have a function for it, with a name describing what happens:

function expandNumberToTwoDigits(str) {
    return ('0'+str).slice(-2)
}

But seeing that what we actually want to do ist: convert an integer < 256 (and >= 0) to a two-digit hex value.

function byteToHex(number) {
    ('0'+(number).toString(16)).slice(-2);
}

The call site would then look something like this:

const tempDay = byteToHex(2 * day);

@@ -16,55 +16,251 @@ module.exports = {
notificationCharacteristic: 'd0e8434dcd290996af416c90f4e0eb2a',
serviceUuid: '3e135142654f9090134aa6ff5bb77046',
payload: {
getInfo: () => new Buffer('03', 'hex'),
getSysInfo: () => new Buffer('00', 'hex'),
Copy link

@thomasnordquist thomasnordquist Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for the changing the payload / buffer value?
Won't this break compatibility with other devices?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:). i remember now. I wondered why all my valves dates were screwed. 03 is setdate, and if any command is sent without the correct payload, the device seems to use either the last payload for that command, or maybe even just the remainign bytes of the last payload! So using 03 was setting the date back to the last date set by the phone app..... 00 seems to be the right command to send without a payload to get the status.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, so to get the status response, you must set the time/date. See earlier mods in eq3Device to change the getInfo call on the device level to call setDatetime. getSysInfo is a NEW command which gets a serial number, etc. (unknown format).

// start firmware update
return {
firwareupdate:true,
raw:info,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixed indentation

break;
}
return {
firwareupdate:true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixed indentation

const minute = date.getMinutes().toString(16)
const second = date.getSeconds().toString(16)
return new Buffer(prefix + year + month + day + hour + minute + second, 'hex')
var out = new Buffer(7);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixed indentation

setDay: (day) => {
var out = new Buffer(16);
out[0] = 0x10;
out[1] = day.day;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixed indentation

@btsimonh
Copy link
Author

btsimonh commented Dec 7, 2018

@thomasnordquist - ready for re-review :).
p.s. the NR node itself is ineffective for me at the moment because of the quantity of devices I have (10).
But would be good to test the node itself with these changes, so that @alikh31 can merge with confidence.

my development flow is here:
https://gist.github.com/btsimonh/178e1e736c7e4a8b0a549991c9331d7c
note that I have 'require' in my settings file (live on the edge!).
This flow is by no means final, but what it attempts to do is:
Only connect to one device at a time (to get over BLE connection limits on platforms).
Serialise all commands.
You can ignore the mqtt aspects for now; but I will want to use the 'eq3interface' with BLE data over MQTT in the end.
I'm on RPi3 with latest(?) bluez (pain in the a**e).
I'm considering something like: shmuelzon/esp32-ble2mqtt#6
but esp32 too unstable for the moment.

Off topic, but I can highly recommend this thermometer for ease of BLE integration, unless you are concerned about data security... (info is sent in advertisment, so simple, and no connection required).

@thomasnordquist
Copy link

I installed your branch and everything checks out so far.
I'd like to extend the functionality of the actual node-red node as well, so expect a PR on your branch later today 😅.

Copy link

@thomasnordquist thomasnordquist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more review and a potential "bug"

}

// +-7 degrees

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't resist

Suggested change
// +-7 degrees
// temerature offset ranging from -7 to +7 degrees celsius

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree; expect in your pull request :)


// read any return, and convert to a javascript structure
// main oare function, which then defers to fiunctions above as required.
parseInfo: function(info) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that you reduced this function to pattern-matching only! 👍

module.exports = {
writeCharacteristic: '3fa4585ace4a3baddb4bb8df8179ea09',
notificationCharacteristic: 'd0e8434dcd290996af416c90f4e0eb2a',
serviceUuid: '3e135142654f9090134aa6ff5bb77046',
payload: {
getInfo: () => new Buffer('03', 'hex'),
getSysInfo: () => new Buffer('00', 'hex'), // note change from 03 - 03 is set date, and was RESETTING date every call.
Copy link

@thomasnordquist thomasnordquist Dec 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment why something has changed, should, in my opinion, be the responsibility the commit message which introduced the change. Referring to something that is no longer part of the code serves no purpose.

It's perfectly fine not to have the comment here, I only marked the place in the review because I was not able to explain the change with my limited knowledge.

setDatetime also documents the 0x03 code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, the comment serves no purpose if you did not know is was getInfo before.... pls remove in pull request :)

dst: (statusMask & status.dst) === status.dst,
openWindow: (statusMask & status.openWindow) === status.openWindow,
lowBattery: (statusMask & status.lowBattery) === status.lowBattery,
valvePosition,
targetTemperature,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to duplicatevalvePosition and targetTemperature ?

lowBattery: (statusMask & status.lowBattery) === status.lowBattery,
valvePosition,
targetTemperature,
ecoendtime: ecoendtime,
},
valvePosition,
targetTemperature,
};
},

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh.. just so as not to break someone else who was using the output; yet they were 'in the wrong place' in my thinking, so I wanted to move them gracefully.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see your point. Nothing wrong with changing things. Probably it was not wise to name the group status since everything here can be considered status 🤨
I see no harm if it stays how it is.

EQ3BLE.prototype.getInfo = function() {
return this.writeAndGetNotification(eq3interface.payload.getInfo())
.then(info => eq3interface.parseInfo(info))
return this.writeAndGetNotification(eq3interface.payload.setDatetime(new Date()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaks setDateTime(date)?!

setting the date will overwritten everytime one tries to get the info.

Is this still true since you changed the getInfo buffer from 0x03 to 0x00?

// this sets the date; else the date can get set to old data in the buffer!!!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the right date? not sure how else to trigger the status :). just copying the android app....

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, let me get back to you. I had some very nice reverse-engineered documents from someone else somewhere.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, I believe the engineers made the decision to update the thermostats clock whenever a phone is connected. Mixing responsibilities of functions is not a very nice thing to do...

By the way, the documentation I mentioned: https://github.com/Heckie75/eQ-3-radiator-thermostat/blob/master/eq-3-radiator-thermostat-api.md

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice documentation. I'm sure we don't cover it all.

@thomasnordquist
Copy link

By the way, nice thermometer. Even if it had no bluetooth 😅
I'll definitely order one.

@btsimonh
Copy link
Author

p.s. my dev flow has a fatal (2 clicks and you are dead fatal) flaw.
Referencing noble devices in global in NR 0.19 is BAD for your NR health. They added a 'Context' tab, and it loses ~30Mbytes per browser refresh.
new one here
ref in forum here

@thomasnordquist
Copy link

Can you narrow the problem down? Might be more helpful for the devs if they had something reproducible to toy with. Something like a minimal example without hardware requirements?

@btsimonh
Copy link
Author

I tried, but the noble device is a VERY complex thing. could not reproduce with a simpler (very deep) structure. As I state in the forum, it could be just that the first time it actually fails, and breaks from then on. But it's gonna kill me when I update all my main stuff; I think the avoidance strategy is best; I do some evil stuff with contexts at the moment, all trying to avoid large messages (see 'clone'). To that forum message, I got one reply; I don't think others abuse NR as I do....!

@thomasnordquist
Copy link

Do you maybe have a cyclic reference?
Some tree traversing algorithms don't check for cycles and therefore crash due to a stack overflow / limited memory.
You could try to store something cyclic in the context and see if it crashes 🤨

var circularReference = {};
circularReference.circularReference = circularReference;

JSON.stringify(circularReference) // FAILS because it can detect cycles, and json doesn't support them

@btsimonh
Copy link
Author

yep, tested with that :). browser shows 'Circular', and is fine. it's just one to avoid; there is not value having a large structure in the displayed context; it's great for 'normal' people :).

@btsimonh
Copy link
Author

no, didn't play with it too much; so much guessing. The docs you highlighted are quite good though.....

@@ -11,60 +11,301 @@ const status = {
lowBattery: 128,
}

// convert any number to 2 digits hex.
// ensures integer, and takes last two digits of hex conversion with zero filling to 2 if < 16
var h2 = function(val) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is broken

Suggested change
var h2 = function(val) {
var h2 = function(number) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, never trust an englishman! Had a suspicion that something was up with my rads; now I know - the time has probably been s****d for a month! (actually, date/time seems correct; but I've not run anything more complex than polling since the changes....)

@thomasnordquist
Copy link

thomasnordquist commented Dec 25, 2018 via email

@alikh31
Copy link
Owner

alikh31 commented Jan 23, 2019

Hi @btsimonh and @thomasnordquist sorry I've been away for a while. again since I have no physical devices I can not actually test the code but it looks legit to me. if you guys are happy with it I can merge the pr and release the package.
btw since I am not highly available these days if you guys would like to be added as repo admin let me know so I add you guys.

@janvda
Copy link

janvda commented Mar 24, 2019

Hi @btsimonh ,

I am currently testing branch btsimonh/node-red-contrib-eq3-bluetooth#btsimonh
So it is working very well. Thanks a lot for that.

There are some minor glitches (like "list of all available addressess will be retrieved..." never returning a list) which I want to report as issue on your repository.
But currently I don't have the privilege to report any issue on your repository.
So it would be good if you could enable that feature for others.

kr
Jan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants