From 7095673ed45dbf70b121fe0b103392a402a2e150 Mon Sep 17 00:00:00 2001 From: Christian Oeing Date: Sat, 30 Oct 2021 17:57:41 +0200 Subject: [PATCH] [boschshc] Parental Controls for Thermostats; Wall Thermostats; Internal refactoring (#11134) * Reduce debug output Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Added meta information for Bosch binding Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Set binding online only if fetching rooms and devices worked Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Replaced hard-coded IP address with configuration from things file Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Fixes after rebasing on 2.5.x branch Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Put keystore where openhab user can access it Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Attempt to get a new subscription ID when the old one is invalidated Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Better install script Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Asynchronously get subscription ID Otherwise, code would get stuck on requesting second subscription ID Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Initial steps towards pairing Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Doesn't compile because of bouncycastle - compiles if commented out Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Bumped version Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Added basic support for multiple devices to support Twinguard Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Support for power meter in power switches + all values from Twinguard Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Add window contact to the list of supported things Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Updated README to indicate new supported devices Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Added missing file Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Added motion detector thing Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * All devices support RefreshType now Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Cleanup - removed pairing related stuff that doesn't work Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Fixed a weird bug where InWallSwitchHandler was not an instance of SHCHandler Signed-off-by: Stefan Kaestle Signed-off-by: Gerd Zanker * Update BoschSHCBridgeHandler.java fixed HTTP request URL to get rooms from SHC Signed-off-by: Gerd Zanker * Add handler for Bosch Shutter Control to get and set its open level Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Auto update of .classpath by IDE Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Handle PercentType command to set a specific shutter level Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Put service name in constant instead of using it hard coded twice Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Refactor putState method of BoschSHCBridgeHandler Remove unnecessary parts of the request like Gateway ID and put some general logic into separate methods to reuse them in other functions later Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Send operation state STOPPED to stop shutter from moving Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add shutter control to supported devices in README.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add null reference check to avoid a NullReferenceException in ShutterControlHandler if device state couldn't be fetched Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add TemperatureLevelService Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add ThermostatHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add valve tappet position channel to thermostat via ValveTappet service Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add service registration for BoschSHCHandler, so the state updates are automatically calling the registered state update callback of the handler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add affectedChannels to service registration and handle RefreshType directly in BoschSHCHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Cleaned up DeviceStatusUpdate class Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * added Bosch SHC certs added public certificates from https://github.com/BoschSmartHome/bosch-shc-api-docs/tree/master/best_practice Signed-off-by: Gerd Zanker * added pairing support added support for keystore creation and pairing documented the process in readme refactoring of httpClient to take care of SSL context Signed-off-by: Gerd Zanker * Add RoomClimateControlService and ClimateControlHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Handle command to set setpoint temperature and move conversion from service state data to thing states into service state classes Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Remove unnecessary imports from ThermostatHandler.java Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make new service and handler @NonNullByDefault Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Added comments for climate control service, handler and base service and handler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add missing comments on new classes and their methods Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make registerService method of BoschSHCHandler private and adjust usages Derived handlers should use createService instead. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Throw an error instead of returning null for method getBridgeHandler of BoschSHCHandler This allows for fewer null checks after the initialization of a handler. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add @author tags in JavaDoc of new classes Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Ran mvn spotless:apply to apply correct code formatting Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Fixed missing imports in BoschSHCService.java This was caused by a too quick merge of me. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Fixed warnings about null annotations Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add @NonNullByDefault to all handlers Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * added license header by executing mvn license:format Signed-off-by: Gerd Zanker * #16 Adjust logger usages to not be too verbose Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #10 Remove obsolete parse-things.py script Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #29 Remove dev scripts install.sh and run.sh scripts Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * added author name to each class comment Added the author(s) and small comment to each class based on the git history and if necessary created the basis class comment body. Signed-off-by: Gerd Zanker * added license header for new files Signed-off-by: Gerd Zanker * run mvn spotless check/apply Signed-off-by: Gerd Zanker * #24 Catch error response when trying to get state of a service of a device and throw specific error instead of returning invalid state object Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #24 Run mvn spotless:apply and mvn license:format to respect coding guidelines Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #5 Update README.md with up-to-date information about the setup of the binding Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #31 Typo in README.md Co-authored-by: Gerd Zanker Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add detailed description of the system password to provide in README.md Signed-off-by: Christian Oeing Co-authored-by: Gerd Zanker Signed-off-by: Gerd Zanker * Fix non-initialized member of @NonNullByDefault class ValveTappetServiceState Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #7 Moved each device to a separate subfolder inside devices folder Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #44 Adjust version of org.openhab.addons.reactor.bundles to 2.5.9-SNAPSHOT in pom.xml Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #45 Increase year in copyright headers Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #45 Remove several obsolete loggers and fix logging of exceptions Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #45 Fixed some code analysis warnings Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #45 Ran mvn spotless:apply Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #45 Revert adding @NonNullByDefault to BoschSHCConfiguration Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #47 Fixed code analysis warnings Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * updated text files based on pull request feedback Signed-off-by: Gerd Zanker * Apply suggestions from code review Applied only suggested string text changes and few trivial code changes Co-authored-by: Hilbrand Bouwkamp Co-authored-by: Fabian Wolter Signed-off-by: Gerd Zanker * Improved comments and changed visibility code review findings improved related to code comments and public/private visibility of variables removed example properties file and class path entries Signed-off-by: Gerd Zanker * added @NonNullByDefault annotation where necessary and easy possible in addition minor improvements like removed TODO for code refactoring used BoschSHCException instead of Error in one place Renamed internal Error class to ErroInfo ran spotless:apply Signed-off-by: Gerd Zanker * use SIUnits replaced all Celcius units with eclipse SIUnits Co-authored-by: Hilbrand Bouwkamp Signed-off-by: Gerd Zanker * change logging and GSON related code increased many log levels avoid creating new GSON instances in derived handler subclasses changed catch code to avoid stack trace dumps Signed-off-by: Gerd Zanker * Add @NonNullByDefault to BoschSHCConfiguration class and remove obsolete usages of configuration in handlers Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Throw BoschSHCExceptions instead of generic Errors and handle them during creation of services Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use Class::new supplier function instead of deprecated Class.newInstance() method Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Remove @NonNullByDefault from inner classes and unused logger member to remove compile warnings Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use Base64.getEncoder().encodeToString instead of Base64.getEncoder().encode and a manual conversion to String Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add @NonNullByDefault annotation to BoschSHCBridgeConfiguration and check for empty password and ip address in configuration Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use try-with-resources to auto-close streams for key store creation Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move data transfer objects of bridge into dto folder Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add thing type ids and channel type ids to README.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move thing configurations out of thing-types.xml and into configs.xml. Remove deprecated required element and use attribute instead. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Adjust some elements, units, descriptions and labels in configs.xml and thing-types.xml Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move url and request creation from BoschSHCBridgeHandler to BoschHttpClient to reuse it inside BoschHttpClient Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add default timeout to request to smart home controller Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make httpClient of BoschSHCBridgeHandler @Nullable and use BoschHttpClient createUrl and createRequest methods where possible Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make gson field in BoschSHCBridgeHandler final Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Simplify getDevices method of BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use getContentAsString() instead of getContent() plus manual conversion to String Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use instanceof instead of isInstance(...) in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add registerService method to BoschSHCHandler to register already created services, so they can be created in the constructor of a handler and do not have to be @Nullable Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Using simpler String.format instead of MessageFormatter in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move ShutterControlState into dto subfolder and remove @NonNullByDefault annotation Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make bridgeHandler and deviceId in BoschSHCService @Nullable Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move DTOs in dto subfolders and remove @NonNullByDefault annotations from them Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add texts for errors during initialization of bridge Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Migrate PowerSwitch to new service architecture to get rid of BoschSHCBridgeHandler.updateSwitchState Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Remove obsolete null parameter from subscription request Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Removed obsolete TODO about hard-coded data which does not exist anymore Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use lambda for response handling of long poll Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Remove obsolete @NonNull annotations in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Throw http exceptions when trying to request state from a device to set the thing to offline Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Add state options for combined-rating, temperature-rating and humidity-rating channels Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Fix several minor static code analysis warnings Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Added migration artifacts Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Fix formatting and increase version number Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * fix HTTP client issue, improve connection logging, add Developers markdown the HTTPClient requests were broken updated exception handling and logging for connection and pairing describing source of certificates Signed-off-by: Gerd Zanker * Use service for shutter control handling instead of having the logic inside the ShutterControlHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #47 Fix code formatting to remove code analysis warnings Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Fixed pairing Signed-off-by: Gerd Zanker * Fixed long poll Signed-off-by: Gerd Zanker * Move common error handling for parsing responses from BoschHttpClient into sendRequest method to make subscribe request logic clearer Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use scheduler.schedule instead of Thread.sleep during long polling Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Using synchronous request for subscribe request The initialization is not finished without a successful subscription. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use synchronous long poll request to get it to work Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make long polling asynchronous to not block scheduler threads Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Only scheduling new long polls while bridge is not disposed and aborting long polling on disposal Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Move long polling logic into separate class Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Make long polling requests synchronous again, the asynchronous way still does not receive any state updates Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * replaced while loop for pairing with scheduler calls Pattern from LongPolling reused. Scheduling new initial access checks including pairing every 15 seconds until it was successful and long polling can be started Signed-off-by: Gerd Zanker * #47 Throw BoschSHCException instead of raw error to avoid code analysis warning Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #47 Remove TODO from code and add issue #55 instead Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #47 Pass non-null httpClient to bridge initialization instead of having to check for null reference Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Use one supportsThingTypes mapping to list the supported things with their handler in BoschSHCHandlerFactory Previously an array plus a big switch was required. Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #47 Starting http client before scheduling initial access Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * Use logger.debug instead of logger.info Signed-off-by: Christian Oeing Co-authored-by: Connor Petty Signed-off-by: Gerd Zanker * Include cause exception in the PairingFailedException Signed-off-by: Christian Oeing Co-authored-by: Connor Petty Signed-off-by: Gerd Zanker * #14 Move fields above constructor in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove obsolete configuration field from BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Move fields and constructor in JsonRpcRequest to top of the class Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Log the failure of a long poll as warning instead of error Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Use %s instead of {} as placeholder for String.format Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Move @Nullable annotation in front of field name Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Specify UTF_8 as charset to convert string to byte array Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove spaces from pem files Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Change ArrayList to List in Device.java Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Use QuantityType for power and energy consumption of the in-wall switch handler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Check if StopMoveType is STOP in ShutterControlHandler before setting the device state Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Make fields of DeviceService final Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Stop http client and cancel scheduled pairing on bridge disposal Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove channel check in BoschTwinguardHandler for Refresh command Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add exception message to warning when update in BoschTwinguardHandler and WindowContactHandler returns incorrect state Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add additional information about channels to thing-types.xml and README.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove confusing comment from LongPolling.subscribe method Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Removed obsolete error log in LongPolling.subscribe A warning is already logged in BoschBridgeHandler when an exception occurs Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add detailed description for purity channel Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Fix typo in DEVELOPERS.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Change scheduledPairing field in BoschSHCBridgeHandler to be @Nullable Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Change type of channel values from DecimalType to QuantityType in BoschTwinguardHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove obsolete .classpath and .project files Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Fixed typo in DEVELOPERS.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Only log error message instead of whole stack trace in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove exception which is not thrown and typo in BoschSslUtil Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Use SmartHomeUnits instead of AbstractUnit in BoschTwinguardHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Added supported-bridge-type-refs to thing-types.xml Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove invalid whitespace from thing-types.xml Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Fixed warnings and errors caused by updated Gson library Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Update info about auto reload of bundle jar in DEVELOPERS.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Update information about adding items and things via UI, added missing password configuration value and changed headline in README.md Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Execute long polling requests asynchronous to not block a thread Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Refactored WindowContactHandler to use ShutterContactService instead of implementing service logic itself Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove obsolete .gitignore. The ignored files are already ignored by the root .gitignore Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Rename constant supportsThingTypes to SUPPORTED_THING_TYPES in BoschSHCHandlerFactory Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Use List.of instead of Arrays.asList Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Call super.dispose last in BoschSHCBridgeHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add exception message to status when http connection to controller fails Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Handle JsonSyntaxException in BoschHttpClient.sendRequest Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Forward InterruptedExceptions to callers, so they have to be handled correctly Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Logging long poll error message and code instead of hash Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Throwing InterruptedException during pairing instead of only logging it Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Move nested class AbortLongPolling to end of LongPolling class Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Make gson instance static final in BoschHttpClient Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Rename gson to GSON and make it static final in BoschSHCHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Remove @Nullable annotations from GSON-created objects Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add causing exception to ExecutionException in BoschHttpClient.sendRequest Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * #14 Add JsonSyntaxException to definition of processUpdate in BoschTwinguardHandler Signed-off-by: Christian Oeing Signed-off-by: Gerd Zanker * fixed keyStore creation Moved PEM files into expected folder according to package Signed-off-by: Gerd Zanker * moved final fields above non-final fields Signed-off-by: Gerd Zanker * updated copyright year in header by executing mvn license:format Signed-off-by: Gerd Zanker * fixed last two pending warnings added back the removed @Nullable annotation in sendRequest() replaced deprecated SslContextFactory constructor call Signed-off-by: Gerd Zanker * added Bouncy Castle third-party license info into NOTICE Signed-off-by: Gerd Zanker * changed Bouncy Castle version to same version as currently used in org.openhab.core.io.jetty.certificate to later reduce download size Signed-off-by: Gerd Zanker * #62 Correctly check if long poll response is valid GSON will not return null if there is no "result" field, but will just set the "result" member to null. Signed-off-by: Christian Oeing * Add @NonNullByDefault annotation to LongPollResultTest class and fix method name Signed-off-by: Christian Oeing * added first unittests for BoschSslUtil class Signed-off-by: Gerd Zanker * added next unittests for BoschHttpClient class Signed-off-by: Gerd Zanker * #55 replaced the password with a fixed one The changeable SHC system password for the keystore is replaced by a static string in the code. The keyStore name is now based on SHC ipAddress to support multiple SmartHomeControllers. Signed-off-by: Gerd Zanker * #72 changed use units of measure for the twinguard humidity and purity values all other QuantityTypes in bindingcode are fine Signed-off-by: Gerd Zanker * #77 changed title of binding to Bosch Smart Home Replaced the SHC occurrences with Smart Home, to avoid technical names. Signed-off-by: Gerd Zanker * #62 Try to restart long polling when it fails before taking the thing offline Signed-off-by: Christian Oeing * #62 Run subscribe request on a new thread instead of using the thread of the previous long polling http request This might be the reason why the subscribe request does never finish or finishes with a timeout Signed-off-by: Christian Oeing * #74 Run the whole long polling response handling in a new thread to not get timeout from HTTP client Signed-off-by: Christian Oeing * #74 Schedule initial access when long polling fails unexpected We need to try to reconnect again and again (with 15 seconds between the requests) as the controller may have been restarted (update, manual restart,...). This is already done by the initial access, so I reuse that mechanism. Signed-off-by: Christian Oeing * Use direct formatting of logger.trace instead of String.format Signed-off-by: Christian Oeing Co-authored-by: Gerd Zanker * #76 Use i18n texts instead of raw translations for status messages about failed long polling Signed-off-by: Christian Oeing * #76 Use logger.debug instead of logger.warn for long poll error as it is handled now Signed-off-by: Christian Oeing * #78 defined api-version each HTTP request will use now the defined "avp-version=2.1" for request to the smart home controller Signed-off-by: Gerd Zanker * logging bundle version removed the old static version string access OSGi bundle version information instead Signed-off-by: Gerd Zanker * #75 improved initial access - added isOnline check and isAccessPossible now failed in case HTTPStatus is an error - same HTTPStatus check done to all blocking send() request calls - using i18n strings for all bridge updateStatus calls - skipped the 'controller' and use only 'Bosch Smart Home' in descriptions - added more @Nullable annotations Signed-off-by: Gerd Zanker * #72 changed use units of measure for the twinguard humidity and purity values all other QuantityTypes in bindingcode are fine Signed-off-by: Gerd Zanker * #77 changed title of binding to Bosch Smart Home Replaced the SHC occurrences with Smart Home, to avoid technical names. Signed-off-by: Gerd Zanker * #62 Try to restart long polling when it fails before taking the thing offline Signed-off-by: Christian Oeing * #62 Run subscribe request on a new thread instead of using the thread of the previous long polling http request This might be the reason why the subscribe request does never finish or finishes with a timeout Signed-off-by: Christian Oeing * #74 Run the whole long polling response handling in a new thread to not get timeout from HTTP client Signed-off-by: Christian Oeing * #74 Schedule initial access when long polling fails unexpected We need to try to reconnect again and again (with 15 seconds between the requests) as the controller may have been restarted (update, manual restart,...). This is already done by the initial access, so I reuse that mechanism. Signed-off-by: Christian Oeing * Use direct formatting of logger.trace instead of String.format Signed-off-by: Christian Oeing Co-authored-by: Gerd Zanker * #76 Use i18n texts instead of raw translations for status messages about failed long polling Signed-off-by: Christian Oeing * #76 Use logger.debug instead of logger.warn for long poll error as it is handled now Signed-off-by: Christian Oeing * #78 defined api-version each HTTP request will use now the defined "avp-version=2.1" for request to the smart home controller Signed-off-by: Gerd Zanker * logging bundle version removed the old static version string access OSGi bundle version information instead Signed-off-by: Gerd Zanker * #75 improved initial access - added isOnline check and isAccessPossible now failed in case HTTPStatus is an error - same HTTPStatus check done to all blocking send() request calls - using i18n strings for all bridge updateStatus calls - skipped the 'controller' and use only 'Bosch Smart Home' in descriptions - added more @Nullable annotations Signed-off-by: Gerd Zanker * added newline Signed-off-by: Gerd Zanker * #46 Rename BoschInWallSwitchHandler, BoschTwinguardHandler, BoschSHCBridgeConfiguration and BoschSHCBridgeHandler Signed-off-by: Christian Oeing * #46 Adjust descriptions of things Signed-off-by: Christian Oeing * #40 Use LatestMotionService in MotionDetectorHandler Signed-off-by: Christian Oeing * #40 Use service instead of custom logic in BoschTwinguardHandler Signed-off-by: Christian Oeing * #40 Add PowerMeterService to use in InWallSwitchHandler instead of having the logic directly in the handler Signed-off-by: Christian Oeing * #40 Rename InWallSwitchHandler to LightControlHandler This is the official name in the Bosch API documentation, so we should use it as well (https://apidocs.bosch-smarthome.com/local) Signed-off-by: Christian Oeing * #34 Get device info on thing initialization to check if device exists Signed-off-by: Christian Oeing * #34 Use generic sendRequest method of http client to have consistent error handling Signed-off-by: Christian Oeing * #34 Fix formatting error when logging device info Signed-off-by: Christian Oeing * #83 Add info if a channel is writable to README.md Signed-off-by: Christian Oeing * #84 Adjust/Add descriptions of supported devices Signed-off-by: Christian Oeing * #25 Add humidity level service and wall thermostat handler Signed-off-by: Christian Oeing * #25 Add wall thermostat handler to handler factory Signed-off-by: Christian Oeing * #66 Check type of service state when received by bridge to make sure that it has the expected type Signed-off-by: Christian Oeing * #41 Add child lock service to Thermostat handler and link its state to a "child-lock" channel Signed-off-by: Christian Oeing * Add null check in BoschSHCServiceState.isValid Signed-off-by: Christian Oeing * #41 Use custom channel type for child-lock. Handle setting child lock state. Signed-off-by: Christian Oeing * #101 Store expected state type inside a member instead of a static variable The static variable was stored in the base class, so it was only initialized once even for different state types Signed-off-by: Christian Oeing * #63 Use better identifier for thing that is missing a (valid) bridge Signed-off-by: Christian Oeing * #108 Add changelog to README.md Signed-off-by: Christian Oeing * #109 Catch possible null pointer exception in long poll response handling Signed-off-by: Christian Oeing * Fix potential null pointer access Signed-off-by: Christian Oeing * Fix static code analysis error and 2 warnings Signed-off-by: Christian Oeing * #111 Remove changelog from README.md Signed-off-by: Christian Oeing * #112 Remove JSON from logs Signed-off-by: Christian Oeing Co-authored-by: Stefan Kaestle Co-authored-by: Gerd Zanker Co-authored-by: Christian Oeing Co-authored-by: Hilbrand Bouwkamp Co-authored-by: Fabian Wolter Co-authored-by: Connor Petty --- .../org.openhab.binding.boschshc/README.md | 122 +++++++++++------- .../devices/BoschSHCBindingConstants.java | 3 + .../internal/devices/BoschSHCHandler.java | 103 +++++++++++---- .../devices/BoschSHCHandlerFactory.java | 26 ++-- .../devices/bridge/BoschHttpClient.java | 34 ++++- ...guration.java => BridgeConfiguration.java} | 4 +- ...CBridgeHandler.java => BridgeHandler.java} | 60 +++++++-- .../internal/devices/bridge/LongPolling.java | 8 +- .../internal/devices/bridge/dto/Device.java | 16 ++- .../bridge/dto/DeviceStatusUpdate.java | 4 +- .../devices/bridge/dto/SubscribeResult.java | 4 + .../LightControlHandler.java} | 76 ++++------- .../motiondetector/MotionDetectorHandler.java | 44 ++----- .../shuttercontrol/ShutterControlHandler.java | 2 +- .../devices/thermostat/ThermostatHandler.java | 36 +++++- .../twinguard/BoschTwinguardHandler.java | 92 ------------- .../devices/twinguard/TwinguardHandler.java | 72 +++++++++++ .../wallthermostat/WallThermostatHandler.java | 64 +++++++++ .../windowcontact/WindowContactHandler.java | 2 +- .../internal/services/BoschSHCService.java | 44 ++++--- .../AirQualityLevelService.java | 30 +++++ .../dto/AirQualityLevelServiceState.java} | 6 +- .../services/childlock/ChildLockService.java | 44 +++++++ .../childlock/dto/ChildLockServiceState.java | 34 +++++ .../childlock/dto/ChildLockState.java | 23 ++++ .../services/dto/BoschSHCServiceState.java | 54 ++++++++ .../dto/JsonRestExceptionResponse.java | 12 +- .../humiditylevel/HumidityLevelService.java | 30 +++++ .../dto/HumidityLevelServiceState.java | 41 ++++++ .../latestmotion/LatestMotionService.java | 31 +++++ .../dto/LatestMotionServiceState.java} | 6 +- .../powermeter/PowerMeterService.java | 30 +++++ .../dto/PowerMeterServiceState.java} | 8 +- .../resources/OH-INF/i18n/boschshc.properties | 4 + .../OH-INF/i18n/boschshc_de.properties | 2 + .../resources/OH-INF/thing/thing-types.xml | 41 ++++-- .../devices/bridge/BoschHttpClientTest.java | 2 +- .../dto/BoschSHCServiceStateTest.java | 79 ++++++++++++ 38 files changed, 965 insertions(+), 328 deletions(-) rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/{BoschSHCBridgeConfiguration.java => BridgeConfiguration.java} (84%) rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/{BoschSHCBridgeHandler.java => BridgeHandler.java} (87%) rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/{inwallswitch/BoschInWallSwitchHandler.java => lightcontrol/LightControlHandler.java} (51%) delete mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/{devices/twinguard/dto/AirQualityLevelState.java => services/airqualitylevel/dto/AirQualityLevelServiceState.java} (89%) create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/{devices/motiondetector/dto/LatestMotionState.java => services/latestmotion/dto/LatestMotionServiceState.java} (83%) create mode 100644 bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java rename bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/{devices/inwallswitch/dto/PowerMeterState.java => services/powermeter/dto/PowerMeterServiceState.java} (74%) create mode 100644 bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java diff --git a/bundles/org.openhab.binding.boschshc/README.md b/bundles/org.openhab.binding.boschshc/README.md index 0cb265319b87b..28b791ba7af81 100644 --- a/bundles/org.openhab.binding.boschshc/README.md +++ b/bundles/org.openhab.binding.boschshc/README.md @@ -3,14 +3,16 @@ Binding for the Bosch Smart Home. - [Bosch Smart Home Binding](#bosch-smart-home-binding) + - [Changelog](#changelog) - [Supported Things](#supported-things) - - [Bosch In-Wall switches & Bosch Smart Plugs](#bosch-in-wall-switches--bosch-smart-plugs) - - [Bosch TwinGuard smoke detector](#bosch-twinguard-smoke-detector) - - [Bosch Window/Door contacts](#bosch-windowdoor-contacts) - - [Bosch Motion Detector](#bosch-motion-detector) - - [Bosch Shutter Control in-wall](#bosch-shutter-control-in-wall) - - [Bosch Thermostat](#bosch-thermostat) - - [Bosch Climate Control](#bosch-climate-control) + - [In-Wall switches & Smart Plugs](#in-wall-switches--smart-plugs) + - [TwinGuard smoke detector](#twinguard-smoke-detector) + - [Door/Window contact](#doorwindow-contact) + - [Motion Detector](#motion-detector) + - [Shutter Control](#shutter-control) + - [Thermostat](#thermostat) + - [Climate Control](#climate-control) + - [Wall Thermostat](#wall-thermostat) - [Limitations](#limitations) - [Discovery](#discovery) - [Bridge Configuration](#bridge-configuration) @@ -20,72 +22,98 @@ Binding for the Bosch Smart Home. ## Supported Things -### Bosch In-Wall switches & Bosch Smart Plugs +### In-Wall switches & Smart Plugs + +A simple light control. **Thing Type ID**: `in-wall-switch` -| Channel Type ID | Item Type | Description | -|--------------------|---------------|----------------------------------------------| -| power-switch | Switch | Current state of the switch. | -| power-consumption | Number:Power | Current power consumption (W) of the device. | -| energy-consumption | Number:Energy | Energy consumption of the device. | +| Channel Type ID | Item Type | Writable | Description | +| ------------------ | ------------- | :------: | -------------------------------------------- | +| power-switch | Switch | ☑ | Current state of the switch. | +| power-consumption | Number:Power | ☐ | Current power consumption (W) of the device. | +| energy-consumption | Number:Energy | ☐ | Energy consumption of the device. | + +### TwinGuard smoke detector -### Bosch TwinGuard smoke detector +The Twinguard smoke detector warns you in case of fire and constantly monitors the air. **Thing Type ID**: `twinguard` -| Channel Type ID | Item Type | Description | -|--------------------|----------------------|---------------------------------------------------------------------------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| temperature-rating | String | Rating of the currently measured temperature. | -| humidity | Number:Dimensionless | Current measured humidity. | -| humidity-rating | String | Rating of current measured humidity. | -| purity | Number:Dimensionless | Purity of the air (ppm). Range from 500 to 5500 ppm. A higher value indicates a higher pollution. | -| purity-rating | String | Rating of current measured purity. | -| air-description | String | Overall description of the air quality. | -| combined-rating | String | Combined rating of the air quality. | +| Channel Type ID | Item Type | Writable | Description | +| ------------------ | -------------------- | :------: | ------------------------------------------------------------------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| temperature-rating | String | ☐ | Rating of the currently measured temperature. | +| humidity | Number:Dimensionless | ☐ | Current measured humidity (0 to 100). | +| humidity-rating | String | ☐ | Rating of current measured humidity. | +| purity | Number:Dimensionless | ☐ | Purity of the air (ppm). Range from 500 to 5500 ppm. A higher value indicates a higher pollution. | +| purity-rating | String | ☐ | Rating of current measured purity. | +| air-description | String | ☐ | Overall description of the air quality. | +| combined-rating | String | ☐ | Combined rating of the air quality. | -### Bosch Window/Door contacts +### Door/Window contact + +Detects open windows and doors. **Thing Type ID**: `window-contact` -| Channel Type ID | Item Type | Description | -|-----------------|-----------|------------------------------| -| contact | Contact | Contact state of the device. | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | --------- | :------: | ---------------------------- | +| contact | Contact | ☐ | Contact state of the device. | + +### Motion Detector -### Bosch Motion Detector +Detects every movement through an intelligent combination of passive infra-red technology and an additional temperature sensor. **Thing Type ID**: `motion-detector` -| Channel Type ID | Item Type | Description | -|-----------------|-----------|--------------------------------| -| latest-motion | DateTime | The date of the latest motion. | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | --------- | :------: | ------------------------------ | +| latest-motion | DateTime | ☐ | The date of the latest motion. | -### Bosch Shutter Control in-wall +### Shutter Control + +Control of your shutter to take any position you desire. **Thing Type ID**: `shutter-control` -| Channel Type ID | Item Type | Description | -|-----------------|---------------|------------------------------------------| -| level | Rollershutter | Current open ratio (0 to 100, Step 0.5). | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | ------------- | :------: | ---------------------------------------- | +| level | Rollershutter | ☑ | Current open ratio (0 to 100, Step 0.5). | + +### Thermostat -### Bosch Thermostat +Radiator thermostat **Thing Type ID**: `thermostat` -| Channel Type ID | Item Type | Description | -|-----------------------|----------------------|------------------------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| valve-tappet-position | Number:Dimensionless | Current open ratio of valve tappet (0 to 100). | +| Channel Type ID | Item Type | Writable | Description | +| --------------------- | -------------------- | :------: | ---------------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| valve-tappet-position | Number:Dimensionless | ☐ | Current open ratio of valve tappet (0 to 100). | +| child-lock | Switch | ☑ | Indicates if child lock is active. | + +### Climate Control -### Bosch Climate Control +A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room. **Thing Type ID**: `climate-control` -| Channel Type ID | Item Type | Description | -|----------------------|--------------------|-------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| setpoint-temperature | Number:Temperature | Desired temperature. | +| Channel Type ID | Item Type | Writable | Description | +| -------------------- | ------------------ | :------: | ----------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| setpoint-temperature | Number:Temperature | ☑ | Desired temperature. | + +### Wall Thermostat + +Display of the current room temperature as well as the relative humidity in the room. + +**Thing Type ID**: `wall-thermostat` + +| Channel Type ID | Item Type | Writable | Description | +| --------------- | -------------------- | :------: | ------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| humidity | Number:Dimensionless | ☐ | Current measured humidity (0 to 100). | ## Limitations @@ -112,9 +140,11 @@ This certificate is used for pairing between the Bridge and the Bosch Smart Home Bosch IDs for found devices are displayed in the openHAB log on bootup (`OPENHAB_FOLDER/userdata/logs/openhab.log`) The log can also be called using the following command. + ``` tail -f /var/log/openhab/openhab.log /var/log/openhab/events.log ``` + Alternatively, the log can be viewed using the OpenHab Log Viewer (frontail) via http://openhab:9001. Example: diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java index 47e85f9fcf46c..ab30a07499a57 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java @@ -21,6 +21,7 @@ * * @author Stefan Kästle - Initial contribution * @author Christian Oeing - added Shutter Control, ThermostatHandler + * @author Christian Oeing - Added WallThermostatHandler */ @NonNullByDefault public class BoschSHCBindingConstants { @@ -37,6 +38,7 @@ public class BoschSHCBindingConstants { public static final ThingTypeUID THING_TYPE_SHUTTER_CONTROL = new ThingTypeUID(BINDING_ID, "shutter-control"); public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat"); public static final ThingTypeUID THING_TYPE_CLIMATE_CONTROL = new ThingTypeUID(BINDING_ID, "climate-control"); + public static final ThingTypeUID THING_TYPE_WALL_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wall-thermostat"); // List of all Channel IDs // Auto-generated from thing-types.xml via script, don't modify @@ -56,4 +58,5 @@ public class BoschSHCBindingConstants { public static final String CHANNEL_LEVEL = "level"; public static final String CHANNEL_VALVE_TAPPET_POSITION = "valve-tappet-position"; public static final String CHANNEL_SETPOINT_TEMPERATURE = "setpoint-temperature"; + public static final String CHANNEL_CHILD_LOCK = "child-lock"; } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java index 98e6d856effb0..0500f08394662 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java @@ -22,7 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.services.BoschSHCService; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -117,16 +117,34 @@ public BoschSHCHandler(Thing thing) { */ @Override public void initialize() { - this.config = getConfigAs(BoschSHCConfiguration.class); + var config = this.config = getConfigAs(BoschSHCConfiguration.class); + String deviceId = config.id; + if (deviceId == null || deviceId.isEmpty()) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error.empty-device-id"); + return; + } + + // Try to get device info to make sure the device exists try { - this.initializeServices(); + var bridgeHandler = this.getBridgeHandler(); + var info = bridgeHandler.getDeviceInfo(deviceId); + logger.trace("Device initialized:\n{}", info.toString()); + } catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } - // Mark immediately as online - if the bridge is online, the thing is too. - this.updateStatus(ThingStatus.ONLINE); + // Initialize device services + try { + this.initializeServices(); } catch (BoschSHCException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; } + + this.updateStatus(ThingStatus.ONLINE); } /** @@ -142,18 +160,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { // Refresh state of services that affect the channel for (DeviceService deviceService : this.services) { if (deviceService.affectedChannels.contains(channelUID.getIdWithoutGroup())) { - try { - deviceService.service.refreshState(); - } catch (TimeoutException | ExecutionException | BoschSHCException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, - String.format("Error when trying to refresh state from service %s: %s", - deviceService.service.getServiceName(), e.getMessage())); - } catch (InterruptedException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, - String.format("Interrupted refresh state from service %s: %s", - deviceService.service.getServiceName(), e.getMessage())); - Thread.currentThread().interrupt(); - } + this.refreshServiceState(deviceService.service); } } } @@ -187,14 +194,16 @@ protected void initializeServices() throws BoschSHCException { * @return Bridge handler for this thing handler. Null if no or an invalid bridge was set in the configuration. * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set. */ - protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException { + protected BridgeHandler getBridgeHandler() throws BoschSHCException { Bridge bridge = this.getBridge(); if (bridge == null) { - throw new BoschSHCException(String.format("No valid bridge set for %s", this.getThing())); + throw new BoschSHCException(String.format("No valid bridge set for %s (%s)", this.getThing().getLabel(), + this.getThing().getUID().getAsString())); } - BoschSHCBridgeHandler bridgeHandler = (BoschSHCBridgeHandler) bridge.getHandler(); + BridgeHandler bridgeHandler = (BridgeHandler) bridge.getHandler(); if (bridgeHandler == null) { - throw new BoschSHCException(String.format("Bridge of %s has no valid bridge handler", this.getThing())); + throw new BoschSHCException(String.format("Bridge of %s (%s) has no valid bridge handler", + this.getThing().getLabel(), this.getThing().getUID().getAsString())); } return bridgeHandler; } @@ -213,7 +222,7 @@ protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException { return null; } try { - BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler(); + BridgeHandler bridgeHandler = this.getBridgeHandler(); return bridgeHandler.getState(deviceId, stateName, classOfT); } catch (TimeoutException | ExecutionException | BoschSHCException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, @@ -264,7 +273,7 @@ protected , TState extends BoschSHCServ protected , TState extends BoschSHCServiceState> void registerService( TService service, Consumer stateUpdateListener, Collection affectedChannels) throws BoschSHCException { - BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler(); + BridgeHandler bridgeHandler = this.getBridgeHandler(); String deviceId = this.getBoschID(); if (deviceId == null) { @@ -299,6 +308,54 @@ protected , TState extends BoschSHCServ } } + /** + * Lets a service handle a received command. + * Sets the status of the device to offline if handling the command fails. + * + * @param Type of service. + * @param Type of service state. + * @param service Service which should handle command. + * @param command Command to handle. + */ + protected , TState extends BoschSHCServiceState> void handleServiceCommand( + TService service, Command command) { + try { + if (command instanceof RefreshType) { + this.refreshServiceState(service); + } else { + TState state = service.handleCommand(command); + this.updateServiceState(service, state); + } + } catch (BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + String.format("Error when service %s should handle command %s: %s", service.getServiceName(), + command.getClass().getName(), e.getMessage())); + } + } + + /** + * Requests a service to refresh its state. + * Sets the device offline if request fails. + * + * @param Type of service. + * @param Type of service state. + * @param service Service to refresh state for. + */ + private , TState extends BoschSHCServiceState> void refreshServiceState( + TService service) { + try { + service.refreshState(); + } catch (TimeoutException | ExecutionException | BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + String.format("Error when trying to refresh state from service %s: %s", service.getServiceName(), + e.getMessage())); + } catch (InterruptedException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String + .format("Interrupted refresh state from service %s: %s", service.getServiceName(), e.getMessage())); + Thread.currentThread().interrupt(); + } + } + /** * Registers a service of this device. * diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java index 19a1e94198250..556a4fff69f5c 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java @@ -12,14 +12,7 @@ */ package org.openhab.binding.boschshc.internal.devices; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHC; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_THERMOSTAT; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_TWINGUARD; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; import java.util.Collection; import java.util.List; @@ -27,13 +20,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler; -import org.openhab.binding.boschshc.internal.devices.inwallswitch.BoschInWallSwitchHandler; +import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler; import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler; import org.openhab.binding.boschshc.internal.devices.shuttercontrol.ShutterControlHandler; import org.openhab.binding.boschshc.internal.devices.thermostat.ThermostatHandler; -import org.openhab.binding.boschshc.internal.devices.twinguard.BoschTwinguardHandler; +import org.openhab.binding.boschshc.internal.devices.twinguard.TwinguardHandler; +import org.openhab.binding.boschshc.internal.devices.wallthermostat.WallThermostatHandler; import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -50,6 +44,7 @@ * * @author Stefan Kästle - Initial contribution * @author Christian Oeing - Added Shutter Control and ThermostatHandler; refactored handler mapping + * @author Christian Oeing - Added WallThermostatHandler */ @NonNullByDefault @Component(configurationPid = "binding.boschshc", service = ThingHandlerFactory.class) @@ -66,14 +61,15 @@ public ThingTypeHandlerMapping(ThingTypeUID thingTypeUID, Function SUPPORTED_THING_TYPES = List.of( - new ThingTypeHandlerMapping(THING_TYPE_SHC, thing -> new BoschSHCBridgeHandler((Bridge) thing)), - new ThingTypeHandlerMapping(THING_TYPE_INWALL_SWITCH, BoschInWallSwitchHandler::new), - new ThingTypeHandlerMapping(THING_TYPE_TWINGUARD, BoschTwinguardHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_SHC, thing -> new BridgeHandler((Bridge) thing)), + new ThingTypeHandlerMapping(THING_TYPE_INWALL_SWITCH, LightControlHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_TWINGUARD, TwinguardHandler::new), new ThingTypeHandlerMapping(THING_TYPE_WINDOW_CONTACT, WindowContactHandler::new), new ThingTypeHandlerMapping(THING_TYPE_MOTION_DETECTOR, MotionDetectorHandler::new), new ThingTypeHandlerMapping(THING_TYPE_SHUTTER_CONTROL, ShutterControlHandler::new), new ThingTypeHandlerMapping(THING_TYPE_THERMOSTAT, ThermostatHandler::new), - new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new)); + new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_WALL_THERMOSTAT, WallThermostatHandler::new)); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java index f342153d55d76..b6c7e345578b0 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java @@ -24,6 +24,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -34,6 +36,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -251,7 +254,7 @@ public Request createRequest(String url, HttpMethod method, @Nullable Object con if (content != null) { String body = GSON.toJson(content); - logger.trace("create request for {} and content {}", url, body); + logger.trace("create request for {} and content {}", url, content.toString()); request = request.content(new StringContentProvider(body)); } else { logger.trace("create request for {}", url); @@ -265,26 +268,45 @@ public Request createRequest(String url, HttpMethod method, @Nullable Object con * * @param request Request to send * @param responseContentClass Type of expected response + * @param contentValidator Checks if the parsed response is valid + * @param errorResponseHandler Optional ustom error response handling. If not provided a generic exception is thrown * @throws ExecutionException in case of invalid HTTP request result * @throws TimeoutException in case of an HTTP request timeout * @throws InterruptedException in case of an interrupt + * @throws BoschSHCException in case of a custom handled error response */ - public TContent sendRequest(Request request, Class responseContentClass) - throws InterruptedException, TimeoutException, ExecutionException { + public TContent sendRequest(Request request, Class responseContentClass, + Predicate contentValidator, + @Nullable BiFunction errorResponseHandler) + throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { logger.trace("Send request: {}", request.toString()); ContentResponse contentResponse = request.send(); - logger.debug("Received response: {} - status: {}", contentResponse.getContentAsString(), - contentResponse.getStatus()); + String textContent = contentResponse.getContentAsString(); + + Integer statusCode = contentResponse.getStatus(); + if (!HttpStatus.getCode(statusCode).isSuccess()) { + if (errorResponseHandler != null) { + throw errorResponseHandler.apply(statusCode, textContent); + } else { + throw new ExecutionException(String.format("Request failed with status code %s", statusCode), null); + } + } + + logger.debug("Received response: {} - status: {}", textContent, statusCode); try { @Nullable - TContent content = GSON.fromJson(contentResponse.getContentAsString(), responseContentClass); + TContent content = GSON.fromJson(textContent, responseContentClass); if (content == null) { throw new ExecutionException(String.format("Received no content in response, expected type %s", responseContentClass.getName()), null); } + if (!contentValidator.test(content)) { + throw new ExecutionException(String.format("Received invalid content for type %s: %s", + responseContentClass.getName(), content), null); + } return content; } catch (JsonSyntaxException e) { throw new ExecutionException(String.format("Received invalid content in response, expected type %s: %s", diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java similarity index 84% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java index 3943f1432846a..92187160a0a14 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java @@ -15,12 +15,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link BoschSHCBridgeConfiguration} class contains fields mapping thing configuration parameters. + * The {@link BridgeConfiguration} class contains fields mapping thing configuration parameters. * * @author Stefan Kästle - Initial contribution */ @NonNullByDefault -public class BoschSHCBridgeConfiguration { +public class BridgeConfiguration { /** * IP address of the Bosch Smart Home Controller diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java similarity index 87% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java index c575d7586d456..f04272c1ccad7 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java @@ -61,9 +61,9 @@ * @author Christian Oeing - refactorings of e.g. server registration */ @NonNullByDefault -public class BoschSHCBridgeHandler extends BaseBridgeHandler { +public class BridgeHandler extends BaseBridgeHandler { - private final Logger logger = LoggerFactory.getLogger(BoschSHCBridgeHandler.class); + private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class); /** * gson instance to convert a class to json string and back. @@ -79,7 +79,7 @@ public class BoschSHCBridgeHandler extends BaseBridgeHandler { private @Nullable ScheduledFuture scheduledPairing; - public BoschSHCBridgeHandler(Bridge bridge) { + public BridgeHandler(Bridge bridge) { super(bridge); this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure); @@ -91,7 +91,7 @@ public void initialize() { FrameworkUtil.getBundle(getClass()).getVersion()); // Read configuration - BoschSHCBridgeConfiguration config = getConfigAs(BoschSHCBridgeConfiguration.class); + BridgeConfiguration config = getConfigAs(BridgeConfiguration.class); String ipAddress = config.ipAddress.trim(); if (ipAddress.isEmpty()) { @@ -274,8 +274,8 @@ private boolean getDevices() throws InterruptedException { for (Device d : devices) { // Write found devices into openhab.log until we have implemented auto discovery logger.info("Found device: name={} id={}", d.name, d.id); - if (d.deviceSerivceIDs != null) { - for (String s : d.deviceSerivceIDs) { + if (d.deviceServiceIds != null) { + for (String s : d.deviceServiceIds) { logger.info(".... service: {}", s); } } @@ -300,7 +300,14 @@ private boolean getDevices() throws InterruptedException { private void handleLongPollResult(LongPollResult result) { for (DeviceStatusUpdate update : result.result) { if (update != null && update.state != null) { - logger.debug("Got update for {}", update.deviceId); + logger.debug("Got update of type {}: {}", update.type, update.state); + + var updateDeviceId = update.deviceId; + if (updateDeviceId == null) { + continue; + } + + logger.debug("Got update for {}", updateDeviceId); boolean handled = false; @@ -315,10 +322,11 @@ private void handleLongPollResult(LongPollResult result) { String deviceId = handler.getBoschID(); handled = true; - logger.debug("Registered device: {} - looking for {}", deviceId, update.deviceId); + logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId); - if (deviceId != null && update.deviceId.equals(deviceId)) { - logger.debug("Found child: {} - calling processUpdate with {}", handler, update.state); + if (deviceId != null && updateDeviceId.equals(deviceId)) { + logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler, update.id, + update.state); handler.processUpdate(update.id, update.state); } } else { @@ -327,7 +335,7 @@ private void handleLongPollResult(LongPollResult result) { } if (!handled) { - logger.debug("Could not find a thing for device ID: {}", update.deviceId); + logger.debug("Could not find a thing for device ID: {}", updateDeviceId); } } } @@ -400,6 +408,34 @@ private boolean getRooms() throws InterruptedException { } } + public Device getDeviceInfo(String deviceId) + throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException { + @Nullable + BoschHttpClient httpClient = this.httpClient; + if (httpClient == null) { + throw new BoschSHCException("HTTP client not initialized"); + } + + String url = httpClient.getBoschSmartHomeUrl(String.format("devices/%s", deviceId)); + Request request = httpClient.createRequest(url, GET); + + return httpClient.sendRequest(request, Device.class, Device::isValid, (Integer statusCode, String content) -> { + JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class); + if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) { + if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) { + return new BoschSHCException("@text/offline.conf-error.invalid-device-id"); + } else { + return new BoschSHCException( + String.format("Request for info of device %s failed with status code %d and error code %s", + deviceId, errorResponse.statusCode, errorResponse.errorCode)); + } + } else { + return new BoschSHCException(String.format("Request for info for device %s failed with status code %d", + deviceId, statusCode)); + } + }); + } + /** * Query the Bosch Smart Home Controller for the state of the given thing. * @@ -445,7 +481,7 @@ private boolean getRooms() throws InterruptedException { } @Nullable - T state = gson.fromJson(content, stateClass); + T state = BoschSHCServiceState.fromJson(content, stateClass); if (state == null) { throw new BoschSHCException(String.format("Received invalid, expected type %s", stateClass.getName())); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java index 9b7b254493eb3..1e34c96eb6cc3 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java @@ -109,14 +109,16 @@ private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedExc String url = httpClient.getBoschShcUrl("remote/json-rpc"); JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe", new String[] { "com/bosch/sh/remote/*", null }); - logger.debug("Subscribe: Sending request: {} - using httpClient {}", gson.toJson(request), httpClient); + logger.debug("Subscribe: Sending request: {} - using httpClient {}", request.toString(), + httpClient.toString()); Request httpRequest = httpClient.createRequest(url, POST, request); - SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class); + SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class, + SubscribeResult::isValid, null); logger.debug("Subscribe: Got subscription ID: {} {}", response.getResult(), response.getJsonrpc()); String subscriptionId = response.getResult(); return subscriptionId; - } catch (TimeoutException | ExecutionException e) { + } catch (TimeoutException | ExecutionException | BoschSHCException e) { throw new LongPollingFailedException( String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()), e); diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java index 562e075ed3dd3..6e616a6d4cde3 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java @@ -45,7 +45,7 @@ public class Device { public String rootDeviceId; public String id; - public List deviceSerivceIDs; + public List deviceServiceIds; public String manufacturer; public String roomId; public String deviceModel; @@ -54,4 +54,18 @@ public class Device { public String name; public String status; public List childDeviceIds; + + public static Boolean isValid(Device obj) { + return obj != null && obj.id != null; + } + + @Override + public String toString() { + return String.format( + "Type %s; RootDeviceId: %s; Id: %s; Device Service Ids: %s; Manufacturer: %s; Room Id: %s; Device Model: %s; Serial: %s; Profile: %s; Name: %s; Status: %s; Child Device Ids: %s ", + this.type, this.rootDeviceId, this.id, + this.deviceServiceIds != null ? String.join(", ", this.deviceServiceIds) : "null", this.manufacturer, + this.roomId, this.deviceModel, this.serial, this.profile, this.name, this.status, + this.childDeviceIds != null ? String.join(", ", this.childDeviceIds) : "null"); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java index 649f3de5bfda4..6cce6f455f8db 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.boschshc.internal.devices.bridge.dto; +import org.eclipse.jdt.annotation.Nullable; + import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; @@ -47,7 +49,7 @@ public class DeviceStatusUpdate { /** * Id of device the update is for. */ - public String deviceId; + public @Nullable String deviceId; @Override public String toString() { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java index c0df59e65480c..e920decd07b1a 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java @@ -30,4 +30,8 @@ public String getResult() { public String getJsonrpc() { return this.jsonrpc; } + + public static Boolean isValid(SubscribeResult obj) { + return obj != null && obj.result != null && obj.jsonrpc != null; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java similarity index 51% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java index b5a4ebd449ca5..9825db8f43c94 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.inwallswitch; +package org.openhab.binding.boschshc.internal.devices.lightcontrol; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; @@ -21,8 +21,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.inwallswitch.dto.PowerMeterState; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.powermeter.PowerMeterService; +import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState; import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService; import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState; import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState; @@ -32,22 +33,19 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; -import com.google.gson.JsonElement; - /** - * Represents Bosch in-wall switches. + * A simple light control. * * @author Stefan Kästle - Initial contribution */ @NonNullByDefault -public class BoschInWallSwitchHandler extends BoschSHCHandler { +public class LightControlHandler extends BoschSHCHandler { private final PowerSwitchService powerSwitchService; - public BoschInWallSwitchHandler(Thing thing) { + public LightControlHandler(Thing thing) { super(thing); this.powerSwitchService = new PowerSwitchService(); } @@ -57,46 +55,32 @@ protected void initializeServices() throws BoschSHCException { super.initializeServices(); this.registerService(this.powerSwitchService, this::updateChannels, List.of(CHANNEL_POWER_SWITCH)); + this.createService(PowerMeterService::new, this::updateChannels, + List.of(CHANNEL_POWER_CONSUMPTION, CHANNEL_ENERGY_CONSUMPTION)); } @Override public void handleCommand(ChannelUID channelUID, Command command) { super.handleCommand(channelUID, command); - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); - - if (command instanceof RefreshType) { - switch (channelUID.getId()) { - case CHANNEL_POWER_CONSUMPTION: { - PowerMeterState state = this.getState("PowerMeter", PowerMeterState.class); - if (state != null) { - updatePowerMeterState(state); - } - break; + switch (channelUID.getId()) { + case CHANNEL_POWER_SWITCH: + if (command instanceof OnOffType) { + updatePowerSwitchState((OnOffType) command); } - case CHANNEL_ENERGY_CONSUMPTION: - // Nothing to do here, since the same update is received from POWER_CONSUMPTION - break; - default: - logger.warn("Received refresh request for unsupported channel: {}", channelUID); - } - } else { - switch (channelUID.getId()) { - case CHANNEL_POWER_SWITCH: - if (command instanceof OnOffType) { - updatePowerSwitchState((OnOffType) command); - } - break; - } + break; } } - void updatePowerMeterState(PowerMeterState state) { - logger.debug("Parsed power meter state of {}: energy {} - power {}", this.getBoschID(), state.energyConsumption, - state.energyConsumption); - - updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType(state.powerConsumption, Units.WATT)); - updateState(CHANNEL_ENERGY_CONSUMPTION, new QuantityType(state.energyConsumption, Units.WATT_HOUR)); + /** + * Updates the channels which are linked to the {@link PowerMeterService} of the device. + * + * @param state Current state of {@link PowerMeterService}. + */ + private void updateChannels(PowerMeterServiceState state) { + super.updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType(state.powerConsumption, Units.WATT)); + super.updateState(CHANNEL_ENERGY_CONSUMPTION, + new QuantityType(state.energyConsumption, Units.WATT_HOUR)); } /** @@ -114,20 +98,4 @@ private void updatePowerSwitchState(OnOffType command) { state.switchState = PowerSwitchState.valueOf(command.toFullString()); this.updateServiceState(this.powerSwitchService, state); } - - @Override - public void processUpdate(String id, JsonElement state) { - super.processUpdate(id, state); - - logger.debug("in-wall switch: received update: ID {} state {}", id, state); - - if (id.equals("PowerMeter")) { - PowerMeterState powerMeterState = GSON.fromJson(state, PowerMeterState.class); - if (powerMeterState == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - } else { - updatePowerMeterState(powerMeterState); - } - } - } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java index 6e3f564fe7f1e..a5dc356d5f7b4 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java @@ -14,22 +14,22 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_LATEST_MOTION; +import java.util.List; + import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.motiondetector.dto.LatestMotionState; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.latestmotion.LatestMotionService; +import org.openhab.binding.boschshc.internal.services.latestmotion.dto.LatestMotionServiceState; import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -import com.google.gson.JsonElement; /** - * MotionDetectorHandler + * Detects every movement through an intelligent combination of passive infra-red technology and an additional + * temperature sensor. * * @author Stefan Kästle - Initial contribution + * @author Christian Oeing - Use service instead of custom logic */ @NonNullByDefault public class MotionDetectorHandler extends BoschSHCHandler { @@ -39,34 +39,14 @@ public MotionDetectorHandler(Thing thing) { } @Override - public void handleCommand(ChannelUID channelUID, Command command) { - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); + protected void initializeServices() throws BoschSHCException { + super.initializeServices(); - if (CHANNEL_LATEST_MOTION.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - LatestMotionState state = this.getState("LatestMotion", LatestMotionState.class); - if (state != null) { - updateLatestMotionState(state); - } - } - } + this.createService(LatestMotionService::new, this::updateChannels, List.of(CHANNEL_LATEST_MOTION)); } - void updateLatestMotionState(LatestMotionState state) { + private void updateChannels(LatestMotionServiceState state) { DateTimeType date = new DateTimeType(state.latestMotionDetected); updateState(CHANNEL_LATEST_MOTION, date); } - - @Override - public void processUpdate(String id, JsonElement state) { - logger.debug("Motion detector: received update: {} {}", id, state); - - @Nullable - LatestMotionState latestMotionState = GSON.fromJson(state, LatestMotionState.class); - if (latestMotionState == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - return; - } - updateLatestMotionState(latestMotionState); - } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java index 6b54503744e20..3dc767b4e2216 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java @@ -30,7 +30,7 @@ import org.openhab.core.types.Command; /** - * Handler for a shutter control device + * Control of your shutter to take any position you desire. * * @author Christian Oeing - Initial contribution */ diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java index 5f71040558522..7e2b0e26d3ceb 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.boschshc.internal.devices.thermostat; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CHILD_LOCK; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_VALVE_TAPPET_POSITION; @@ -20,11 +21,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.childlock.ChildLockService; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState; import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService; import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState; import org.openhab.binding.boschshc.internal.services.valvetappet.ValveTappetService; import org.openhab.binding.boschshc.internal.services.valvetappet.dto.ValveTappetServiceState; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; /** * Handler for a thermostat device. @@ -34,18 +39,34 @@ @NonNullByDefault public final class ThermostatHandler extends BoschSHCHandler { + private ChildLockService childLockService; + public ThermostatHandler(Thing thing) { super(thing); + this.childLockService = new ChildLockService(); } @Override protected void initializeServices() throws BoschSHCException { this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE)); this.createService(ValveTappetService::new, this::updateChannels, List.of(CHANNEL_VALVE_TAPPET_POSITION)); + this.registerService(this.childLockService, this::updateChannels, List.of(CHANNEL_CHILD_LOCK)); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + + switch (channelUID.getId()) { + case CHANNEL_CHILD_LOCK: + this.handleServiceCommand(this.childLockService, command); + break; + } } /** - * Updates the channels which are linked to the {@link TemperatureLevelService} of the device. + * Updates the channels which are linked to the {@link TemperatureLevelService} + * of the device. * * @param state Current state of {@link TemperatureLevelService}. */ @@ -54,11 +75,22 @@ private void updateChannels(TemperatureLevelServiceState state) { } /** - * Updates the channels which are linked to the {@link ValveTappetService} of the device. + * Updates the channels which are linked to the {@link ValveTappetService} of + * the device. * * @param state Current state of {@link ValveTappetService}. */ private void updateChannels(ValveTappetServiceState state) { super.updateState(CHANNEL_VALVE_TAPPET_POSITION, state.getPositionState()); } + + /** + * Updates the channels which are linked to the {@link ChildLockService} of the + * device. + * + * @param state Current state of {@link ChildLockService}. + */ + private void updateChannels(ChildLockServiceState state) { + super.updateState(CHANNEL_CHILD_LOCK, state.getActiveState()); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java deleted file mode 100644 index b2f5a1a83e0ea..0000000000000 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.boschshc.internal.devices.twinguard; - -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; - -import javax.measure.quantity.Dimensionless; -import javax.measure.quantity.Temperature; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.twinguard.dto.AirQualityLevelState; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -import com.google.gson.JsonElement; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link BoschSHCHandler} is responsible for handling commands for the TwinGuard handler. - * - * @author Stefan Kästle - Initial contribution - */ -@NonNullByDefault -public class BoschTwinguardHandler extends BoschSHCHandler { - - public BoschTwinguardHandler(Thing thing) { - super(thing); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - Bridge bridge = this.getBridge(); - - if (bridge != null) { - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); - - if (command instanceof RefreshType) { - AirQualityLevelState state = this.getState("AirQualityLevel", AirQualityLevelState.class); - if (state != null) { - updateAirQualityState(state); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Bridge is NUL"); - } - } - - void updateAirQualityState(AirQualityLevelState state) { - updateState(CHANNEL_TEMPERATURE, new QuantityType(state.temperature, SIUnits.CELSIUS)); - updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating)); - updateState(CHANNEL_HUMIDITY, new QuantityType(state.humidity, Units.PERCENT)); - updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating)); - updateState(CHANNEL_PURITY, new QuantityType(state.purity, Units.PARTS_PER_MILLION)); - updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description)); - updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating)); - updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating)); - } - - @Override - public void processUpdate(String id, JsonElement state) throws JsonSyntaxException { - logger.debug("Twinguard: received update: {} {}", id, state); - - AirQualityLevelState parsed = GSON.fromJson(state, AirQualityLevelState.class); - if (parsed == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - return; - } - - logger.debug("Parsed switch state of {}: {}", this.getBoschID(), parsed); - updateAirQualityState(parsed); - } -} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java new file mode 100644 index 0000000000000..0b10500fce67e --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.devices.twinguard; + +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_AIR_DESCRIPTION; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_COMBINED_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE_RATING; + +import java.util.List; + +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.AirQualityLevelService; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.dto.AirQualityLevelServiceState; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; + +/** + * The Twinguard smoke detector warns you in case of fire and constantly monitors the air. + * + * @author Stefan Kästle - Initial contribution + * @author Christian Oeing - Use service instead of custom logic + */ +@NonNullByDefault +public class TwinguardHandler extends BoschSHCHandler { + + public TwinguardHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeServices() throws BoschSHCException { + super.initializeServices(); + + this.createService(AirQualityLevelService::new, this::updateChannels, + List.of(CHANNEL_TEMPERATURE, CHANNEL_TEMPERATURE_RATING, CHANNEL_HUMIDITY, CHANNEL_HUMIDITY_RATING, + CHANNEL_PURITY, CHANNEL_PURITY_RATING, CHANNEL_AIR_DESCRIPTION, CHANNEL_COMBINED_RATING)); + } + + private void updateChannels(AirQualityLevelServiceState state) { + updateState(CHANNEL_TEMPERATURE, new QuantityType(state.temperature, SIUnits.CELSIUS)); + updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating)); + updateState(CHANNEL_HUMIDITY, new QuantityType(state.humidity, Units.PERCENT)); + updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating)); + updateState(CHANNEL_PURITY, new QuantityType(state.purity, Units.PARTS_PER_MILLION)); + updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating)); + updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description)); + updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating)); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java new file mode 100644 index 0000000000000..401bf6bcc2c68 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.devices.wallthermostat; + +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService; +import org.openhab.binding.boschshc.internal.services.humiditylevel.dto.HumidityLevelServiceState; +import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService; +import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState; +import org.openhab.core.thing.Thing; + +/** + * Handler for a wall thermostat device. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public final class WallThermostatHandler extends BoschSHCHandler { + + public WallThermostatHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeServices() throws BoschSHCException { + this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE)); + this.createService(HumidityLevelService::new, this::updateChannels, List.of(CHANNEL_HUMIDITY)); + } + + /** + * Updates the channels which are linked to the {@link TemperatureLevelService} of the device. + * + * @param state Current state of {@link TemperatureLevelService}. + */ + private void updateChannels(TemperatureLevelServiceState state) { + super.updateState(CHANNEL_TEMPERATURE, state.getTemperatureState()); + } + + /** + * Updates the channels which are linked to the {@link HumidityLevelService} of the device. + * + * @param state Current state of {@link HumidityLevelService}. + */ + private void updateChannels(HumidityLevelServiceState state) { + super.updateState(CHANNEL_HUMIDITY, state.getHumidityState()); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java index 90b978a20430d..502187e5ed409 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java @@ -27,7 +27,7 @@ import org.openhab.core.types.State; /** - * The {@link BoschSHCHandler} is responsible for handling Bosch window/door contacts. + * Detects open windows and doors. * * @author Stefan Kästle - Initial contribution */ diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java index b58d1c3240656..9a9e045ca8827 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java @@ -18,25 +18,26 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonElement; /** - * Base class of a service of a Bosch Smart Home device. - * The services of the devices and their official APIs can be found here: https://apidocs.bosch-smarthome.com/local/ + * Base class of a service of a Bosch Smart Home device. The services of the + * devices and their official APIs can be found here: + * https://apidocs.bosch-smarthome.com/local/ * * @author Christian Oeing - Initial contribution */ @NonNullByDefault public abstract class BoschSHCService { - private final Logger logger = LoggerFactory.getLogger(BoschSHCService.class); + protected final Logger logger = LoggerFactory.getLogger(BoschSHCService.class); /** * Unique service name @@ -48,15 +49,10 @@ public abstract class BoschSHCService { */ private final Class stateClass; - /** - * gson instance to convert a class to json string and back. - */ - private final Gson gson = new Gson(); - /** * Bridge to use for communication from/to the device */ - private @Nullable BoschSHCBridgeHandler bridgeHandler; + private @Nullable BridgeHandler bridgeHandler; /** * Id of device the service belongs to @@ -72,7 +68,8 @@ public abstract class BoschSHCService { * Constructor * * @param serviceName Unique name of the service. - * @param stateClass State class that this service uses for data transfers from/to the device. + * @param stateClass State class that this service uses for data transfers + * from/to the device. */ protected BoschSHCService(String serviceName, Class stateClass) { this.serviceName = serviceName; @@ -84,9 +81,10 @@ protected BoschSHCService(String serviceName, Class stateClass) { * * @param bridgeHandler Bridge to use for communication from/to the device * @param deviceId Id of device this service is for - * @param stateUpdateListener Function to call when a state update was received from the device. + * @param stateUpdateListener Function to call when a state update was received + * from the device. */ - public void initialize(BoschSHCBridgeHandler bridgeHandler, String deviceId, + public void initialize(BridgeHandler bridgeHandler, String deviceId, @Nullable Consumer stateUpdateListener) { this.bridgeHandler = bridgeHandler; this.deviceId = deviceId; @@ -142,7 +140,7 @@ public void refreshState() throws InterruptedException, TimeoutException, Execut if (deviceId == null) { return null; } - BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler; + BridgeHandler bridgeHandler = this.bridgeHandler; if (bridgeHandler == null) { return null; } @@ -162,7 +160,7 @@ public void setState(TState state) throws InterruptedException, TimeoutException if (deviceId == null) { return; } - BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler; + BridgeHandler bridgeHandler = this.bridgeHandler; if (bridgeHandler == null) { return; } @@ -176,7 +174,7 @@ public void setState(TState state) throws InterruptedException, TimeoutException */ public void onStateUpdate(JsonElement stateData) { @Nullable - TState state = gson.fromJson(stateData, this.stateClass); + TState state = BoschSHCServiceState.fromJson(stateData, this.stateClass); if (state == null) { this.logger.warn("Received invalid, expected type {}", this.stateClass.getName()); return; @@ -195,4 +193,16 @@ private void onStateUpdate(TState state) { stateUpdateListener.accept(state); } } + + /** + * Allows a service to handle a command and create a new state out of it. + * The new state still has to be set via setState. + * + * @param command Command to handle + * @throws BoschSHCException If service can not handle command + */ + public TState handleCommand(Command command) throws BoschSHCException { + throw new BoschSHCException( + String.format("%s: Can not handle command %s", this.getServiceName(), command.getClass().getName())); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java new file mode 100644 index 0000000000000..7c864606df341 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.airqualitylevel; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.dto.AirQualityLevelServiceState; + +/** + * This service constantly measures key air quality values to help you create a healthy room climate. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class AirQualityLevelService extends BoschSHCService { + + public AirQualityLevelService() { + super("AirQualityLevel", AirQualityLevelServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java similarity index 89% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java index 724377b53e011..dd26764ce5d10 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.twinguard.dto; +package org.openhab.binding.boschshc.internal.services.airqualitylevel.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -19,9 +19,9 @@ * * @author Stefan Kästle - Initial contribution */ -public class AirQualityLevelState extends BoschSHCServiceState { +public class AirQualityLevelServiceState extends BoschSHCServiceState { - public AirQualityLevelState() { + public AirQualityLevelServiceState() { super("airQualityLevelState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java new file mode 100644 index 0000000000000..079ad8e5caa8d --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.childlock; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockState; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.Command; + +/** + * Indicates if child lock of device is active. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class ChildLockService extends BoschSHCService { + public ChildLockService() { + super("Thermostat", ChildLockServiceState.class); + } + + @Override + public ChildLockServiceState handleCommand(Command command) throws BoschSHCException { + if (command instanceof OnOffType) { + ChildLockServiceState state = new ChildLockServiceState(); + state.childLock = ChildLockState.valueOf(command.toFullString()); + return state; + } else { + return super.handleCommand(command); + } + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java new file mode 100644 index 0000000000000..1a1bc34c48fa7 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.childlock.dto; + +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.library.types.OnOffType; + +/** + * State for {@link ChildLockService} to activate and deactivate the child lock + * of a device. + * + * @author Christian Oeing - Initial contribution + */ +public class ChildLockServiceState extends BoschSHCServiceState { + public ChildLockServiceState() { + super("childLockState"); + } + + public ChildLockState childLock; + + public OnOffType getActiveState() { + return OnOffType.from(this.childLock.toString()); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java new file mode 100644 index 0000000000000..d53b4054231a4 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.childlock.dto; + +/** + * Possible values for {@link ChildLockServiceState}. + * + * @author Christian Oeing - Initial contribution + */ +public enum ChildLockState { + ON, + OFF +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java index 0ffbfaf14859e..b9aa2ee016f58 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.boschshc.internal.services.dto; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; /** @@ -20,14 +26,62 @@ * @author Christian Oeing - Initial contribution */ public class BoschSHCServiceState { + + /** + * gson instance to convert a class to json string and back. + */ + private static final Gson gson = new Gson(); + + private static final Logger logger = LoggerFactory.getLogger(BoschSHCServiceState.class); + + /** + * State type. Initialized when instance is created. + */ + private @Nullable String stateType = null; + @SerializedName("@type") private final String type; protected BoschSHCServiceState(String type) { this.type = type; + + if (stateType == null) { + stateType = type; + } } public String getType() { return type; } + + protected boolean isValid() { + String expectedType = stateType; + if (expectedType == null || !expectedType.equals(this.type)) { + var className = this.getClass().getName(); + logger.debug("Expected state type {} for state class {}, received {}", expectedType, className, this.type); + return false; + } + + return true; + } + + public static @Nullable TState fromJson(String json, + Class stateClass) { + var state = gson.fromJson(json, stateClass); + if (state == null || !state.isValid()) { + return null; + } + + return state; + } + + public static @Nullable TState fromJson(JsonElement json, + Class stateClass) { + var state = gson.fromJson(json, stateClass); + if (state == null || !state.isValid()) { + return null; + } + + return state; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java index cb8994bad4442..f19d71eb12673 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java @@ -18,6 +18,12 @@ * @author Christian Oeing - Initial contribution */ public class JsonRestExceptionResponse extends BoschSHCServiceState { + + /** + * The entity could not be found. One of the defined path parameters was invalid. + */ + public static final String ENTITY_NOT_FOUND = "ENTITY_NOT_FOUND"; + public JsonRestExceptionResponse() { super("JsonRestExceptionResponseEntity"); this.errorCode = ""; @@ -32,5 +38,9 @@ public JsonRestExceptionResponse() { /** * The HTTP status of the error. */ - public int statusCode; + public Integer statusCode; + + public static boolean isValid(JsonRestExceptionResponse obj) { + return obj != null && obj.errorCode != null && obj.statusCode != null; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java new file mode 100644 index 0000000000000..a2a5bf9fb7533 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.humiditylevel; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.humiditylevel.dto.HumidityLevelServiceState; + +/** + * Measures the humidity at a central point in the room. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class HumidityLevelService extends BoschSHCService { + + public HumidityLevelService() { + super("HumidityLevel", HumidityLevelServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java new file mode 100644 index 0000000000000..bd8f249296c53 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.humiditylevel.dto; + +import javax.measure.quantity.Dimensionless; + +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; + +/** + * State for {@link HumidityLevelService} to get and set the desired temperature of a room. + * + * @author Christian Oeing - Initial contribution + */ +public class HumidityLevelServiceState extends BoschSHCServiceState { + + public HumidityLevelServiceState() { + super("humidityLevelState"); + } + + /** + * Current measured humidity. + */ + public double humidity; + + public State getHumidityState() { + return new QuantityType(this.humidity, Units.PERCENT); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java new file mode 100644 index 0000000000000..8e4e941558b52 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.latestmotion; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.latestmotion.dto.LatestMotionServiceState; + +/** + * Detects every movement through an intelligent combination of passive infra-red technology and an additional + * temperature sensor. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class LatestMotionService extends BoschSHCService { + + public LatestMotionService() { + super("LatestMotion", LatestMotionServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java similarity index 83% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java index 38658342c3b88..40f591450db58 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.motiondetector.dto; +package org.openhab.binding.boschshc.internal.services.latestmotion.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -33,9 +33,9 @@ * * @author Stefan Kästle - Initial contribution */ -public class LatestMotionState extends BoschSHCServiceState { +public class LatestMotionServiceState extends BoschSHCServiceState { - public LatestMotionState() { + public LatestMotionServiceState() { super("latestMotionState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java new file mode 100644 index 0000000000000..099eea7f04dd8 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.powermeter; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState; + +/** + * With this service you always have an eye on energy consumption. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class PowerMeterService extends BoschSHCService { + + public PowerMeterService() { + super("PowerMeter", PowerMeterServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java similarity index 74% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java index 9e0ba54f824ed..21b62509fa8b4 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.inwallswitch.dto; +package org.openhab.binding.boschshc.internal.services.powermeter.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; /** - * PowerMeterState + * State for {@link PowerMeterService} * * @author Stefan Kästle - Initial contribution */ -public class PowerMeterState extends BoschSHCServiceState { +public class PowerMeterServiceState extends BoschSHCServiceState { - public PowerMeterState() { + public PowerMeterServiceState() { super("powerMeterState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties index 9946491aaa9d3..329090d4d84bc 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties @@ -10,3 +10,7 @@ offline.conf-error-ssl = The SSL connection to the Bosch Smart Home Controller i offline.long-polling-failed.http-client-null = Long polling failed and could not be restarted because http client is null. offline.long-polling-failed.trying-to-reconnect = Long polling failed, will try to reconnect. offline.interrupted = Conneting to Bosch Smart Home Controller was interrupted. + +offline.conf-error.empty-device-id = No device ID set. + +offline.conf-error.invalid-device-id = Device ID is invalid. diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties index 9c493160f5344..9990709602593 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties @@ -7,3 +7,5 @@ binding.boschshc.description = Dieses Binding integriert das Bosch Smart Home Sy offline.conf-error-pairing = Bitte betätigen Sie den Taster am Bosch Smart Home Controller zum automatischen Verbinden. offline.not-reachable = Smart Home Controller ist nicht erreichbar. offline.conf-error-ssl = Die SSL Verbindung zum Bosch Smart Home Controller ist nicht möglich. +offline.conf-error.empty-device-id = Keine Geräte ID gesetzt. +offline.conf-error.invalid-device-id = Geräte ID ist ungültig. diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml index aa1e30609b087..0af967fe0a45b 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml @@ -18,7 +18,7 @@ - Bosch In-wall switch for light control + A simple light control. @@ -36,7 +36,7 @@ - Bosch TwinGuard environmental sensor + The Twinguard smoke detector warns you in case of fire and constantly monitors the air. @@ -58,8 +58,8 @@ - - Bosch Contact for windows and doors + + Detects open windows and doors. @@ -75,7 +75,8 @@ - Bosch Motion Detector + Detects every movement through an intelligent combination of passive infra-red technology and an + additional temperature sensor. @@ -91,7 +92,7 @@ - Bosch Shutter Control + Control of your shutter to take any position you desire. @@ -107,11 +108,12 @@ - Bosch Thermostat + Radiator thermostat + @@ -124,7 +126,7 @@ - Bosch Climate Control. This is a virtual device which is automatically created for all rooms that have + This is a virtual device which is automatically created for all rooms that have thermostats in it. @@ -136,6 +138,23 @@ + + + + + + + Display of the current room temperature as well as the relative humidity in the room. + + + + + + + + + + Number:Temperature @@ -259,4 +278,10 @@ + + Switch + + Indicates if it is possible to set the desired temperature on the device. + + diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java index 8fa5c61a268a0..4972d1f715d86 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java @@ -107,7 +107,7 @@ void sendRequest() { Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET); // Null pointer exception is expected, because localhost will not answer request assertThrows(NullPointerException.class, () -> { - httpClient.sendRequest(request, SubscribeResult.class); + httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null); }); } } diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java new file mode 100644 index 0000000000000..17914d3f4e790 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.services.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +/** + * Test class + * + * @author Christian Oeing - Initial contribution + */ +class TestState extends BoschSHCServiceState { + public TestState() { + super("testState"); + } +} + +/** + * Test class + * + * @author Christian Oeing - Initial contribution + */ +class TestState2 extends BoschSHCServiceState { + public TestState2() { + super("testState2"); + } +} + +/** + * Unit tests for BoschSHCServiceStateTest + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class BoschSHCServiceStateTest { + private final Gson gson = new Gson(); + + @Test + public void fromJson_nullStateForDifferentType() { + var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class), + TestState.class); + assertEquals(null, state); + } + + @Test + public void fromJson_stateObjectForValidJson() { + var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), + TestState.class); + assertNotEquals(null, state); + } + + /** + * This checks for a bug we had where the expected type stayed the same for different state classes + */ + @Test + public void fromJson_stateObjectForValidJsonAfterOtherState() { + BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class); + var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class), + TestState2.class); + assertNotEquals(null, state2); + } +}