-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[mideaac] Initial contribution- second try #17749
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
base: main
Are you sure you want to change the base?
Conversation
8fae5fa
to
44bb90f
Compare
4ef6b08
to
071c1e7
Compare
This is not forgotten, the time I have to spare is consumed by the ZUI binding at the moment. So don’t wait for me. |
@lsiepel Well I'm not really waiting. I'm working on other things too. There shouldn't be much to review after you rewrote the connection manager. I did a few things to make it more robust to run without the A/C connected to the cloud and fixed some things that you left hanging. I have had it polling for months without issues. It is not exactly A/C season, so no one is exactly clamoring for an update. Hopefully you or someone else can have a look in a couple of months as we get towards spring. EDIT: I did update the version to OH5 and change the date to 2025 in the headers |
7350e50
to
02ae747
Compare
Forgot about this one, sorry. I might have some time to review in the next days. |
Yes, it is working fine. I have been using it since the last commit. Decided to rebase today in case something in the OH5.0 causes a problem, but it passed checks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for not leaving this PR/binding. It has improved, i also see more room for improvement. Besides some new minor issues that i probably missed before, i think the DTO and connection structure can benefit from some more work and thinking.
...binding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/MideaACConfiguration.java
Outdated
Show resolved
Hide resolved
...binding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/MideaACConfiguration.java
Outdated
Show resolved
Hide resolved
...binding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/MideaACConfiguration.java
Outdated
Show resolved
Hide resolved
...es/org.openhab.binding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/Utils.java
Outdated
Show resolved
Hide resolved
...es/org.openhab.binding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/Utils.java
Outdated
Show resolved
Hide resolved
...nding.mideaac/src/main/java/org/openhab/binding/mideaac/internal/handler/MideaACHandler.java
Outdated
Show resolved
Hide resolved
} else { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format( | ||
"Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error")); | ||
logger.warn("Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logging is not needed when the thing state is set.
logger.warn("Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See question above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.warn("Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok in next commit
...mideaac/src/main/java/org/openhab/binding/mideaac/internal/connection/ConnectionManager.java
Outdated
Show resolved
Hide resolved
...mideaac/src/main/java/org/openhab/binding/mideaac/internal/connection/ConnectionManager.java
Outdated
Show resolved
Hide resolved
try { | ||
Thread.sleep(5000); | ||
} catch (InterruptedException ex) { | ||
logger.debug("An interupted error (socket retry) has occured {}", ex.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop processing and exit immediate when an InterruptException occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I recall that won't work as I intended (with 3 tries). The exceptions aren't serious and are ephemeral in nature. I haven't had that INFO log message in months (after I went from 2 to three tries. That's why I forgot it was there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An InterruptException usually signals an external call to stop the proces / exit the application /shutdown. And therefor the method should exit fast cleaning up any resources but surely not trying to perform another retry attempt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have separated the catches
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marked some previous comments as resolved. Found some new ones, but nothing big. Let's slowly continue and narrow the remaining issues down and fix them together.
if (!deviceIsConnected) { | ||
logger.info("Connected to IP {}", ipAddress); | ||
} | ||
logger.debug("Connected to IP {}", ipAddress); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like double logging. I guess this would be better:
if (!deviceIsConnected) { | |
logger.info("Connected to IP {}", ipAddress); | |
} | |
logger.debug("Connected to IP {}", ipAddress); | |
logger.debug("Connected to IP {}: {}", ipAddress, deviceIsConnected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What the intention here was one INFO for the first time and debug if it was recovering from some problem, but not disconnected. Otherwise there is nothing in the log to show it is running. Note starting date.
2025-01-24 16:45:59.796 [INFO ] [nternal.connection.ConnectionManager] - Connected to IP 192.168.0.246
This is when I updated to the rebased binding
2025-02-19 10:58:07.986 [INFO ] [nternal.connection.ConnectionManager] - Connected to IP 192.168.0.246
This is when I deleted the email and password this morning to recheck that it could be done
2025-02-20 08:54:25.405 [INFO ] [nternal.connection.ConnectionManager] - Connected to IP 192.168.0.246
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment to explain
sendCommand(command, callback); | ||
} else { | ||
droppedCommands = droppedCommands + 1; | ||
logger.info("Problem with reading response, skipping {} skipped count since startup {}", command, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.info("Problem with reading response, skipping {} skipped count since startup {}", command, | |
logger.debug("Problem with reading response, skipping {} skipped count since startup {}", command, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. I used that to track health, but probably no value to user
connectionManager.sendCommand(CommandHelper.handleOffTimer(command, lastresponse), callbackLambda); | ||
} | ||
} catch (MideaConnectionException | MideaAuthenticationException e) { | ||
logger.warn("Unable to proces command: {}", e.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an authentication exception occurs, i guess it is permanenet. When a timeout occurs, it is transient and the binding should recover. You might want to handle both cases seperate.
logger.warn("Unable to proces command: {}", e.getMessage()); | |
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok This was part of the code you rewrote and have never seen an exception, so don't know what else to say.
} else { | ||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format( | ||
"Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error")); | ||
logger.warn("Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.warn("Can't retrieve Token and Key from Cloud; email, password and/or cloud parameter error"); |
if (!config.isValid()) { | ||
logger.warn("Configuration invalid for {}", thing.getUID()); | ||
if (config.isDiscoveryNeeded()) { | ||
logger.warn("Discovery needed, discovering....{}", thing.getUID()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.warn("Discovery needed, discovering....{}", thing.getUID()); |
|
||
## Discovery | ||
|
||
Once the Air Conditioner is on the network (WiFi active) the other required parameters can be discovered automatically. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you provide a lot more information then in the readme. This is what chatgpt makes out of it. Please check and adapt the text so that it gives the user enough guidance to set it up correctly.l
The key parameters—like deviceId, token, key, and version—are automatically discovered once the air conditioner is connected to the WiFi and you’ve provided the necessary cloud and password (unless it's a version 2 device).
However, you’ll still need to manually enter a few parameters such as: Polling time, Socket timeout, Prompt tone.
Frame label="AC Unit" { | ||
Text item=outdoor_temperature label="Outdoor Temperature [%.1f °F]" | ||
Text item=indoor_temperature label="Indoor Temperature [%.1f °F]" | ||
Setpoint item=target_temperature label="Target Temperature [%.1f °F]" minValue=63.0 maxValue=78 step=1.0 | ||
Switch item=power label="Midea AC Power" | ||
Switch item=temperature_unit label= "Temp Unit" mappings=[ON="Fahrenheit", OFF="Celsius"] | ||
Selection item=fan_speed label="Midea AC Fan Speed" | ||
Selection item=operational_mode label="Midea AC Mode" | ||
Selection item=swing_mode label="Midea AC Louver Swing Mode" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frame label="AC Unit" { | |
Text item=outdoor_temperature label="Outdoor Temperature [%.1f °F]" | |
Text item=indoor_temperature label="Indoor Temperature [%.1f °F]" | |
Setpoint item=target_temperature label="Target Temperature [%.1f °F]" minValue=63.0 maxValue=78 step=1.0 | |
Switch item=power label="Midea AC Power" | |
Switch item=temperature_unit label= "Temp Unit" mappings=[ON="Fahrenheit", OFF="Celsius"] | |
Selection item=fan_speed label="Midea AC Fan Speed" | |
Selection item=operational_mode label="Midea AC Mode" | |
Selection item=swing_mode label="Midea AC Louver Swing Mode" | |
Frame label="AC Unit" { | |
Text item=outdoor_temperature label="Outdoor Temperature [%.1f °F]" | |
Text item=indoor_temperature label="Indoor Temperature [%.1f °F]" | |
Setpoint item=target_temperature label="Target Temperature [%.1f °F]" minValue=63.0 maxValue=78 step=1.0 | |
Switch item=power label="Midea AC Power" | |
Switch item=temperature_unit label= "Temp Unit" mappings=[ON="Fahrenheit", OFF="Celsius"] | |
Selection item=fan_speed label="Midea AC Fan Speed" | |
Selection item=operational_mode label="Midea AC Mode" | |
Selection item=swing_mode label="Midea AC Louver Swing Mode" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
Text item=outdoor_temperature label="Outdoor Temperature [%.1f °F]" | ||
Text item=indoor_temperature label="Indoor Temperature [%.1f °F]" | ||
Setpoint item=target_temperature label="Target Temperature [%.1f °F]" minValue=63.0 maxValue=78 step=1.0 | ||
Switch item=power label="Midea AC Power" | ||
Switch item=temperature_unit label= "Temp Unit" mappings=[ON="Fahrenheit", OFF="Celsius"] | ||
Selection item=fan_speed label="Midea AC Fan Speed" | ||
Selection item=operational_mode label="Midea AC Mode" | ||
Selection item=swing_mode label="Midea AC Louver Swing Mode" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a new comment with a suggestion. I meant to add indentation not removing a curly brace.
...ac/src/main/java/org/openhab/binding/mideaac/internal/discovery/MideaACDiscoveryService.java
Show resolved
Hide resolved
try { | ||
Thread.sleep(5000); | ||
} catch (InterruptedException ex) { | ||
logger.debug("An interupted error (socket retry) has occured {}", ex.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An InterruptException usually signals an external call to stop the proces / exit the application /shutdown. And therefor the method should exit fast cleaning up any resources but surely not trying to perform another retry attempt.
In no particular order, 1) I'm not sure how to use the OH standard conversions and where to put the .toLowerCase since I do not see that in the documentation. 2) The exception in the retry I'm trying to address is simply a timeout. 3) Elsewhere, I was trying to avoid breaking something that I fixed earlier would cause me to go back and fix again. It has been months and I'm not recalling all the issues I had getting to the fix. 4) I also attached a log of the DTO actions, it appears to me everything is needed. The only logic is to pull from the cloud responses to compose the next message. EDIT: Regaring other comments; Second I retested a change to remind myself of the problem. I dropped the return after getTokenKeyCloud(cloudProvider); (line 218) and the initialize() after logger.debug("Token and Key obtained from cloud, saving, back to initialize"); (line 372). The token and key are filled in on the UI, the connection manager is started, but chokes on the authorization. Resaving the thing with a change corrects the problem (I just added a location), but that is messy. With the current code everything works seamlessly when deleting the token and key when the cloud provider, password and email are in place. |
Figured out how to leverage the OH core util hex/byte and separated the socket timeout exception from anything else. Seems to still work, but with the magnitude of changes am testing overnight. Have tested scan, retrieve key/token, normal polling and temperature changes. Had one timeout exception that worked as expected. 9 seconds = 4 for timeout and 5 delay for next try.
Normal connection is less that 10ms.
|
2bb3b9c
to
1a97cd8
Compare
ab0cad5
to
6486564
Compare
03e5fbc
to
00ea689
Compare
Ok, so i'll try to reboot this review proces, where are we with this PR? Previously we tried to seperate the handler and the connection manager, how did that work out? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Initial contribution of the MideaAC binding, introducing the core implementation, tests, and OpenHAB metadata for discovery, configuration, and channels.
- Added new binding module and registered it in the parent POM.
- Implemented security, packet/response handling, capability parsing, connection manager, handler logic, and cloud integration.
- Provided comprehensive unit tests and updated thing-types, i18n, and addon metadata.
Reviewed Changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
bundles/pom.xml | Register org.openhab.binding.mideaac module |
src/main/java/.../internal | Add core binding implementation (handler, security, parser, packet, response, energy) |
src/test/java/.../internal | Add unit tests for security, parser, response, energy, discovery, cloud, configuration |
src/main/resources/OH-INF/thing | Define thing-types (channels, configuration) |
src/main/resources/OH-INF/i18n | Provide localization properties |
src/main/resources/OH-INF/addon | Add addon metadata for discovery and binding |
Comments suppressed due to low confidence (2)
bundles/org.openhab.binding.mideaac/src/main/resources/OH-INF/thing/thing-types.xml:275
- The
pattern="%d%%"
implies a percentage but this channel reports energy in kWh; update to a more appropriate numeric or unit-aware pattern (e.g.,%.2f %unit%
).
<state readOnly="true" pattern="%d%%"/>
bundles/org.openhab.binding.mideaac/src/test/java/org/openhab/binding/mideaac/internal/handler/capabilities/CapabilityParserTest.java:152
- [nitpick] Method name
testParseWithtemperature
does not follow camelCase; rename totestParseWithTemperature
for consistency.
void testParseWithtemperature() {
int k = (byte) (crcValue ^ m); | ||
if (k > 256) { | ||
k -= 256; | ||
} | ||
if (k < 0) { | ||
k += 256; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check k > 256
can never be true because k
is derived from a byte; consider normalizing with int k = (crcValue ^ m) & 0xFF;
and remove unreachable branches.
int k = (byte) (crcValue ^ m); | |
if (k > 256) { | |
k -= 256; | |
} | |
if (k < 0) { | |
k += 256; | |
} | |
int k = (crcValue ^ m) & 0xFF; |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this in latest commit
421f838
to
6b575fd
Compare
@lsiepel I didn't see your reopening comment. Must not have been notified (or thought it was related to my ZUI nofications)... To recap; you wrote code to separate the connection manager and the MideaAC handler and I got it working again and addressed all the comments from February 21 and before. Since then I extracted several features from the HA version (and with the help of Copilot in VSC converted them to java). Also there have been a couple of new users of the forum version that had new messages that I incorporated using some Lua code references. Also because of the new features, reworked some part of the code. All that is complete now, but the binding is 25% longer. I was just making a final pass to ensure the ReadMe matched with the additional capabilities. |
Changed version and changed dates (in advance-hopefully ok) Signed-off-by: Bob Eckhoff <[email protected]>
Changing dates early was a bad idea Signed-off-by: Bob Eckhoff <[email protected]>
This reverts commit 44bb90f. Signed-off-by: Bob Eckhoff <[email protected]>
Change to new Headers Signed-off-by: Bob Eckhoff <[email protected]>
Addressed some PR comments. Some questions outstanding. Signed-off-by: Bob Eckhoff <[email protected]>
Additional edits. Signed-off-by: Bob Eckhoff <[email protected]>
Updated utilities to leverage OH core and separated retry timeout from other exceptions. Signed-off-by: Bob Eckhoff <[email protected]>
Eliminated DTO folder and classes by addressing null issues in the cloud communications. Plus slight timing issue on retry. Signed-off-by: Bob Eckhoff <[email protected]>
New standard format? Signed-off-by: Bob Eckhoff <[email protected]>
Change connection log to either/or avoiding both on device being connected. Signed-off-by: Bob Eckhoff <[email protected]>
Minor tweaks from extended testing Signed-off-by: Bob Eckhoff <[email protected]>
Add capability for scheduled token and key updates. Signed-off-by: Bob Eckhoff <[email protected]>
Completed the automatic token key addition with clean-up, additional testing, and eliminated the Clouds class. Developed additional understanding of the token key update to improve the comments. Corrected an order issue in Cloud Provider. Signed-off-by: Bob Eckhoff <[email protected]>
Cleanup unused code in Cloud.java Signed-off-by: Bob Eckhoff <[email protected]>
These changes add the get capability command and sends it to the device during initial initiation, reads the results and populates the properties for easy reference. Signed-off-by: Bob Eckhoff <[email protected]>
Reorganized files and general clean-up of capabilities. Signed-off-by: Bob Eckhoff <[email protected]>
Use the default cloud for faster AC discovery. Signed-off-by: Bob Eckhoff <[email protected]>
Conformed capabilities (properties) to OH standards, added documentation. Changed the ReadMe to fully reflect default options. Changed the text configuration to require IP address. Signed-off-by: Bob Eckhoff <[email protected]>
General review for document clarifications Signed-off-by: Bob Eckhoff <[email protected]>
Forgot to run spotless first Signed-off-by: Bob Eckhoff <[email protected]>
Added capability follow-up command and energy polling, command and reporting. Streamlined response() coding between version 2 and 3. Eliminated alternate target temp for now. Signed-off-by: Bob Eckhoff <[email protected]>
Add energy Polling Refresh, convert frequency to minutes. Edits for clarity Signed-off-by: Bob Eckhoff <[email protected]>
Improve config descriptions and inline documentation Signed-off-by: Bob Eckhoff <[email protected]>
Correct LED command (misread python byte) and change energy polling, even when device is off. There is slight wattage when off to capture. Signed-off-by: Bob Eckhoff <[email protected]>
Introduces a new 'target humidity' channel and associated logic for setting and handling target humidity in dry mode. Adds handling for unsolicited humidity reports (0xA0), updates the command and response processing, extends the callback interface, and provides tests for the new functionality. Updates i18n and thing-type definitions to reflect the new channel. Signed-off-by: Bob Eckhoff <[email protected]>
Replaces 'target-humidity' with 'maximum-humidity' channel and adds 'filter-status' channel for Midea AC binding. Implements handling for new unsolicited temperature (0xA1) and humidity (0xA0, 0xC1) responses, updates command and response processing, and extends tests for new features. Updates documentation and i18n resources to reflect these changes. Signed-off-by: Bob Eckhoff <[email protected]>
Improved documentation in README and JavaDoc comments, clarified terminology (e.g., 'Maximum Humidity'), and updated test method naming for consistency. Fixed CRC8 calculation logic for correctness. Updated thing-types.xml to use correct state patterns for energy channels. Removed outdated example from README and added clarifying notes about channel support. Signed-off-by: Bob Eckhoff <[email protected]>
Minor cleanups of Temperature response and energy formats. Signed-off-by: Bob Eckhoff <[email protected]>
Update pom to 5.1 Signed-off-by: Bob Eckhoff <[email protected]>
Saw that there was a project to add tags to channels, so tried to follow examples from other bindings. Don't have sematic model deployed, so wanted to add to the review process. Signed-off-by: Bob Eckhoff <[email protected]>
This is a second attempt. The first attempt is closed #17395. Essentially all of the previous comments have been addressed. The main new change is the separation of the connection manager and the MideaACHandler, (largely rewritten by @lsiepel) these two files are basically new. I have worked and tested with this version (made some modifications) for several weeks and it seems to work as well as the first attempt now. Also there were a number of new supporting files added by @lsiepel.
For reference the previous intro is here.
I’d like to put this out for new binding review.
This binding was started by the original author as a forum topic but has since had other contributors. I picked it up when I installed a mini-split AC that used the Midea protocol earlier this year, originally to allow for longer polling frequencies. Then as a retirement challenge (no formal java training), decided to attempt to clean it up for official status. I cleaned up the summary report from over 150 items and believe I conformed to current developer guidelines (Java docs, naming, tests, etc.). Along the way added some additional functionality and corrected some code issues.
The discovery and security classes remain largely unchanged from the original author (who I haven’t been able to contact), and work well. The other classes I understand pretty well at this point and hopefully can address any concerns.
As a note, to conform to the guidelines there are breaking changes in naming from the version I published on the forum thread.