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

[onecta] Initial contribution #16730

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open

[onecta] Initial contribution #16730

wants to merge 44 commits into from

Conversation

adr001db
Copy link

@adr001db adr001db commented May 9, 2024

New Binding for Daikin using Onecta.

With the newer Daikin units it is no longer possible to control them directly (as is done in the other Daikin binding). The units can only be connected to the Daikin cloud called Onecta.
The units can then ‘only’ be controlled with the Onecta app on a phone or tablet.
This binding makes it possible to still control the units with OpenHAB. It’s now done by connecting the binding to Daikin’s Onecta.
The unit information is then received from the Daikin cloud just like the app. Commands to the units also run via the Daikin Cloud.
Older units can also be controlled with this binding as long as they are registered in Onecta.

Description

Please give a few sentences describing the overall goals of the pull request.
Give enough details to make the improvement and changes of the PR understandable
to both developers and tech-savy users.

Please keep the following in mind:

Testing

Your pull request will automatically be built and available under the following folder:
https://openhab.jfrog.io/ui/native/libs-pullrequest-local/org/openhab/addons/bundles/

The Jar can be found at:
https://community.openhab.org/t/daikin-onecta-cloud-binding-4-0-0-0-5-0-0-0

Don't forget to submit a thread about your Add-on in the openHAB community:
https://community.openhab.org/c/add-ons
-->

@adr001db adr001db requested a review from a team as a code owner May 9, 2024 09:47
@adr001db adr001db requested a review from mhilbush as a code owner May 9, 2024 10:42
@lsiepel lsiepel added the new binding If someone has started to work on a binding. For a new binding PR. label May 9, 2024
@lsiepel lsiepel changed the title Onecta [onecta] Initial contribution May 9, 2024
@lsiepel
Copy link
Contributor

lsiepel commented May 9, 2024

Thanks for your contribution. As there are many PR's currently being reviewed, it might take some time, evantually it will be picked up by one of the maintainers.

Can you remove the ecobee files? Add yourself to the codewoner file and perform some kind of self-review with this checklist in mind it will speed up the review process if easy spotted issues can be fixed upfront.

Edit: Also the build fails and you might want to check SAT warnings.

Copy link
Contributor

@lsiepel lsiepel left a comment

Choose a reason for hiding this comment

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

Some initial comments after half way review. Ping me when you are ready for resuming

@adr001db
Copy link
Author

Hi @lsiepel,
Thanks for the first comments on my pullrequest.

But can you please help me. I'am struggeling with the First commit. This commit is not mine. I tried to delete it, but I don't think it went well.

What should I do with this ?

Greetings Alexander

afbeelding

@lsiepel lsiepel removed the request for review from mhilbush May 10, 2024 10:13
@lsiepel
Copy link
Contributor

lsiepel commented May 10, 2024

If you refer to DCO, some guidance is here:
https://github.com/openhab/openhab-addons/pull/16730/checks?check_run_id=24814490170

The builds fail to spotless:
https://github.com/openhab/openhab-addons/actions/runs/9030368073/job/24814490996?pr=16730

That commit that you refer to does not seem to be a problem.

@adr001db
Copy link
Author

Thanks than i will ignore it.

@lsiepel
Copy link
Contributor

lsiepel commented May 13, 2024

Commits, reviewers and dco are fixed, now it needs to build. Also some comments to look at.

adr001db and others added 5 commits May 14, 2024 09:25
Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
Solving naming-convention

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
Solving naming-convention

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
Solving naming-convention

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
@adr001db
Copy link
Author

Hi fellow developers,

Yesterday I ran into the problem that I could no longer build my project locally.
Part of the error message was:
`
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.994 s
[INFO] Finished at: 2024-05-22T16:11:49+02:00
[INFO] ------------------------------------------------------------------------

[ERROR] Failed to execute goal org.apache.karaf.tooling:karaf-maven-plugin:4.4.5:verify (karaf-feature-verification) on project org.openhab.binding.onecta: Feature resolution failed for [openhab-binding-onecta/4.2.0.SNAPSHOT]
[ERROR] Message: Unable to resolve root: missing requirement [root] osgi.identity; osgi.identity=openhab-binding-onecta; type=karaf.feature; version=4.2.0.SNAPSHOT; filter:="(&(osgi.identity=openhab-binding-onecta)(type=karaf.feature)(version>=4.2.0.SNAPSHOT))" [caused by: Unable to resolve openhab-binding-onecta/4.2.0.SNAPSHOT: missing requirement [openhab-binding-onecta/4.2.0.SNAPSHOT] osgi.identity; osgi.identity=openhab-runtime-base; type=karaf.feature [caused by: Unable to resolve openhab-runtime-base/4.2.0.SNAPSHOT: missing requirement [openhab-runtime-base/4.2.0.SNAPSHOT] osgi.identity; osgi.identity=openhab-core-model-item; type=karaf.feature [caused by: Unable to resolve openhab-core-model-item/4.2.0.SNAPSHOT: missing requirement [openhab-core-model-item/4.2.0.SNAPSHOT] osgi.identity; osgi.identity=org.openhab.core.model.item; type=osgi.bundle; version="[4.2.0.202405201643,4.2.0.202405201643]"; resolution:=mandatory [caused by: Unable to resolve org.openhab.core.model.item/4.2.0.202405201643: missing requirement [org.openhab.core.model.item/4.2.0.202405201643] osgi.wiring.bundle; osgi.wiring.bundle=org.eclipse.xtext.common.types; filter:="(osgi.wiring.bundle=org.eclipse.xtext.common.types)" [caused by: Unable to resolve org.eclipse.xtext.common.types/2.35.0.v20240430-1012: missing requirement [org.eclipse.xtext.common.types/2.35.0.v20240430-1012] osgi.wiring.bundle; osgi.wiring.bundle=org.objectweb.asm; bundle-version="[9.7.0,9.8.0)"; filter:="(&(osgi.wiring.bundle=org.objectweb.asm)(bundle-version>=9.7.0)(!(bundle-version>=9.8.0)))"]]]]][ERROR] Repositories: {
[ERROR] file:C:\Data\openhab\openhab-addons\bundles\org.openhab.binding.onecta\target/feature/feature.xml
[ERROR] mvn:org.apache.karaf.features/framework/4.4.5/xml/features
[ERROR] mvn:org.apache.karaf.features/specs/4.4.5/xml/features
[ERROR] mvn:org.apache.karaf.features/standard/4.4.5/xml/features
[ERROR] mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/4.2.0-SNAPSHOT/xml/features
[ERROR] mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-tp/4.2.0-SNAPSHOT/xml/features
[ERROR] mvn:org.ops4j.pax.web/pax-web-features/8.0.24/xml/features
[ERROR] }
[ERROR] Resources: {
[ERROR] mvn:com.fasterxml.jackson.core/jackson-annotations/2.16.0
[ERROR] mvn:com.fasterxml.jackson.core/jackson-core/2.16.0
[ERROR] mvn:com.fasterxml.jackson.core/jackson-databind/2.16.0
[ERROR] mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-cbor/2.16.0
[ERROR] mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.16.0
`
After searching I saw that a merge had been done in which the karaf.version had gone from 4.4.5 to 4.4.6.

To solve this I performed a "Sync Fork". And now, fortunately, it works again locally.

My question is, is this the right way? Or is there a better way?

Greetings Alexander

@lsiepel
Copy link
Contributor

lsiepel commented May 22, 2024

To solve this I performed a "Sync Fork". And now, fortunately, it works again locally.

My question is, is this the right way? Or is there a better way?

Some changes (karaf update) have been applied to openhab-addons/main and that sometimes breaks other builds. In that case you have to pull the changes from main into your fork and into your feature branch. That is what you did with this commit: Merge branch 'openhab:main' into onecta' So yes this seems perfectly fine!
A common mistake is that openhab-addons/main is directly pulled into your feature branch (skipping your fork/main) and that generates a lot of crap.

 - Solving naming-convention
 - Moved rawdataLogging in Bridge to trace logging
 - Removed Refreshtoken, openhabhost and stubdatafile from Bridge
 - refreshUnitsData in OnectaConnectionClient.java refactored

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
 - Add Timeout to HttpClients

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
 - Moved 3 Channels to Properties

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
 - Remove generic exceptions

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
@adr001db
Copy link
Author

adr001db commented May 23, 2024

Hi,

I'm in doubt, which is better ?

  if (getManagementPoint(managementPointType) != null
                && getManagementPoint(managementPointType).getTemperatureControl() != null
                && getManagementPoint(managementPointType).getTemperatureControl().getValue() != null
                && getManagementPoint(managementPointType).getTemperatureControl().getValue().getOperationModes() != null
                && getManagementPoint(managementPointType).getTemperatureControl().getValue().getOperationModes().getOperationMode(getCurrentOperationMode()) != null
                && getManagementPoint(managementPointType).getTemperatureControl().getValue().getOperationModes().getOperationMode(getCurrentOperationMode()).getSetpoints() != null
                && getManagementPoint(managementPointType).getTemperatureControl().getValue().getOperationModes().getOperationMode(getCurrentOperationMode()).getSetpoints().getRoomTemperature() != null
        ) {
            return getManagementPoint(this.managementPointType).getTemperatureControl().getValue().getOperationModes()
                    .getOperationMode(getCurrentOperationMode()).getSetpoints().getRoomTemperature().getMinValue();
        } else {
            return null;
        }

or

try {
    return getManagementPoint(this.managementPointType).getTemperatureControl().getValue().getOperationModes()
            .getOperationMode(getCurrentOperationMode()).getSetpoints().getRoomTemperature().getMinValue();
     } catch (NullPointerException e) {
           return null;
    }

I prefer the one with the catch NullPointerException

greetings alexander

 - All If statements with { }
 - some changes in exceptions

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
@david-pace
Copy link
Member

david-pace commented May 24, 2024

I would propose to use Optional.ofNullable(), which can be used for these kind of null check chains:

return Optional.ofNullable(getManagementPoint(this.managementPointType))
    .map(mp -> mp.getTemperatureControl())
    .map(tc -> tc.getValue()
    ...
    .orElse(null);

You can also replace the map() lambdas with method references (your IDE should propose this automatically) 👍

Here are some references:

 - Due to removal of refreshtoken from config property some refactoring needed creating connection to Onecta

Signed-off-by: Alexander Drent <Alex@Drent-ict.nl>
Copy link
Contributor

@lsiepel lsiepel left a comment

Choose a reason for hiding this comment

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

2nd part of the review, still not fully covered, but don't want to hold it.

@@ -0,0 +1,18 @@
done - Refresh token bewaren
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this file from the PR. Also mark it as WIP/DRAFT if not all features are finished. It is also fine to have smallerPR's in thre future, but all breaking changes should be in there now. (like seperating units/watertank)

Comment on lines 40 to 41
public static final String CONFIG_PAR_LOGRAWDATA = "rawdataLogging";
public static final String CONFIG_PAR_STUBDATAFILE = "stubdataFile";
Copy link
Contributor

Choose a reason for hiding this comment

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

These should be removed

  • use logging (trace) if you need to see the response from the device
  • use Unit Tests to upload data files that are supplied to the binding

private @Nullable static HttpClient httpClient = null;

public void setHttpClientFactory(HttpClientFactory httpClientFactory) {
if (this.httpClientFactory == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would assume that in all cases when this method is called, it should set the httpClientFactory, so the null check is not needed.

}

public void setBridgeThing(Thing bridgeThing) {
if (this.bridgeThing == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would assume that in all cases when this method is called, it should set the bridgeThing, so the null check is not needed.

import org.openhab.core.thing.Thing;

/**
* The {@link OnectaConfiguration} class contains fields mapping thing configuration parameters.
Copy link
Contributor

Choose a reason for hiding this comment

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

This class does not hold any fields mapping thing configuration parameters.
It seems to have a handle to httpclientfactory etc and that are not configuration parameters.

updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}

pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevices, 10,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevices, 10,
pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevices, 0

Is there a need to wait 10 seconds to execture the first run?

Comment on lines +117 to +132
if (getThing().getStatus().equals(ThingStatus.OFFLINE)) {
try {
logger.debug("Try to restore connection ");
onectaConnectionClient.restoreConnecton();

if (onectaConnectionClient.isOnline()) {
updateStatus(ThingStatus.ONLINE);
}
} catch (DaikinCommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Try to restore connection. See log for more information. ");
}
}

if (getThing().getStatus().equals(ThingStatus.ONLINE)) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (getThing().getStatus().equals(ThingStatus.OFFLINE)) {
try {
logger.debug("Try to restore connection ");
onectaConnectionClient.restoreConnecton();
if (onectaConnectionClient.isOnline()) {
updateStatus(ThingStatus.ONLINE);
}
} catch (DaikinCommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Try to restore connection. See log for more information. ");
}
}
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {

I would not rely on the ThingStatus to determine if the connection has to be restored or not. The handler should not need to have deepr knowledge of the onectaConnectionClient.

The onectaConnectionClient.refreshUnitsData(); method should have some sanity check and throw an exception like DaikinCommunicationException if there is an unrecoverable issue.

List<Thing> things = getThing().getThings();
for (Thing t : things) {
// BaseThingHandler handler;
if (t.getStatus().equals(ThingStatus.ONLINE)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (t.getStatus().equals(ThingStatus.ONLINE)) {

if (getThing().getStatus().equals(ThingStatus.ONLINE)) {

try {
onectaConnectionClient.refreshUnitsData();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
onectaConnectionClient.refreshUnitsData();
onectaConnectionClient.refreshUnitsData();
updateStatus(ThingStatus.ONLINE);

if (t.getStatus().equals(ThingStatus.ONLINE)) {

if (t.getThingTypeUID().equals(THING_TYPE_CLIMATECONTROL)) {
OnectaDeviceHandler onectaDeviceHandler = (OnectaDeviceHandler) t.getHandler();
Copy link
Contributor

Choose a reason for hiding this comment

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

If all these handlers have an shared interface, this code could be simplified by casting the t.getHandler() to that interface and call refreshdevice. as this is the same for all channels.

@@ -132,7 +132,7 @@ public void pollDevicesOnlineTest() throws NoSuchMethodException, InvocationTarg
Method privateMethod = OnectaBridgeHandler.class.getDeclaredMethod("pollDevices");
privateMethod.setAccessible(true);

when(onectaConnectionClientMock.isOnline()).thenReturn(true);
// when(onectaConnectionClientMock.isOnline()).thenReturn(true);
Copy link
Member

Choose a reason for hiding this comment

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

Please don't commit commented code. Either uncomment it or remove it.

if (!onectaSignInClient.isOnline()) {
onectaSignInClient.signIn();
}
/*
Copy link
Member

Choose a reason for hiding this comment

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

Please don't commit commented code. Either uncomment it or remove it.

}
} catch (DaikinCommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Try to restore connection. See log for more information. ");
Copy link
Member

Choose a reason for hiding this comment

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

Proposal: Trying to restore connection. See log for more information. <-- note that there is an additional space in your message at the end.

Also, messages displayed on the UI should be externalized to a .properties file in order to be translatable. Put them into a file located at src/main/resources/OH-INF/i18n/onecta.properties. Instead of a literal string you can pass @text/my.property.key here, which will look up my.property.key in the i18n file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new binding If someone has started to work on a binding. For a new binding PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants