Compare commits

...

221 Commits
5.0.1 ... main

Author SHA1 Message Date
lsiepel 5ba98f0c5f
[omnilink] Update AlarmMode channels to String type (#19354)
Fixes: https://github.com/openhab/openhab-addons/issues/19343

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-19 23:33:59 +02:00
Nadahar e79d3a6a76
[astro] Various refactoring/fixes (#19310)
* Fix DateTimeUtilsTest default Locale dependency

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
2025-09-19 11:10:54 +02:00
Martin Grassl cbe003ac59
[mybmw] Fix user agent string for stable conection (#19336)
* [mybmw] add stop charging command

Signed-off-by: Martin Grassl <martin.grassl@digital-filestore.de>
2025-09-19 06:18:01 +02:00
David Pace b389bafe44
[boschshc] Add support for presence simulation (#19326)
* [boschshc] Add support for presence simulation

Signed-off-by: David Pace <dev@davidpace.de>
2025-09-18 21:40:31 +02:00
Florian Hotze 08d5c8e385
[fronius] Expose BatteryControl::addSchedule as Thing action (#19352)
* [fronius] Expose BatteryControl::addSchedule as Thing action

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-09-18 20:11:15 +02:00
Florian Hotze 48485705e6
[jsscripting] Upgrade to openhab-js 5.14.0 (#19353)
Changelog: https://github.com/openhab/openhab-js/blob/main/CHANGELOG.md#5140

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-09-18 19:35:30 +02:00
Florian Hotze cd639eafef
[fronius] Fix battery control actions unavailable if FW < 1.36.x (#19349)
#18872 introduced a badly designed if statement that completely disables battery control for firmware < 1.36.x.
Since #18872 added support for FW >= 1.36.x, previous version must be still supported.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-09-18 17:11:30 +02:00
Florian Hotze c42047b636
[fronius] Fix config API authentication for firmware >= 1.38.6 (#19344)
* [fronius] Fix config authentication for firmware >= 1.38.6

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-09-18 07:50:35 +02:00
lsiepel 6393b850eb
[zwavejs] Add RollerShutter support (#19191)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-17 22:50:23 +02:00
Florian Hotze 449fc06bbc
[froniuswattpilot] Use camelCase for HTTP client name (#19345)
Aligns with the naming scheme for thread pools in openHAB.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-09-17 18:54:46 +02:00
Holger Hees 2247c2b825
fix shelly manage ui (#19242) 2025-09-16 21:21:50 +02:00
Joshua Freeman 0d2c48fc7a
[wiz] Fix bugs for improved stability and consistency (#19321)
* Make bug fixes for Wiz binding for stability and consistency.

Signed-off-by: Joshua Freeman <github@frejos.net>
2025-09-16 21:16:59 +02:00
Dan Cunningham c618ffa88b
[matter] Fix bridge fan mode configuration (#19293)
* [matter] Fixes bridge fan mode configuration bug

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
2025-09-16 21:15:01 +02:00
dependabot[bot] 2a972c7a45
Bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.3 to 3.5.4 (#19333)
Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.3 to 3.5.4.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.3...surefire-3.5.4)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 09:01:04 +02:00
dependabot[bot] 131e317c5d
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.3 to 3.5.4 (#19334)
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.5.3 to 3.5.4.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.3...surefire-3.5.4)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
  dependency-version: 3.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 08:57:18 +02:00
mjagdis 07f232af39
[tuya] Improve connection handling (#18829)
* [tuya] Fix format of DP_REFRESH payload

Signed-off-by: Mike Jagdis <mjagdis@eris-associates.co.uk>
2025-09-15 07:23:07 +02:00
Holger Friedrich c4dceefccd
Activate javaagent for tests to avoid JDK warnings (#19329)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-14 23:20:19 +02:00
Holger Friedrich 3942fd29da
[knx] Fix test warning (#19330)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-14 23:06:42 +02:00
lsiepel cbfff2b2cd
Add unit conversion (#19325)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-14 10:58:51 +02:00
lo92fr 93cc3ab06c
[linky] Fix new url schema for getMeasures() related to September Enedis changes to WebAPI (#19289)
* fix new url schema for getMeasures():
- add segment to URL
- use new userId in URL
- fix entry point from v1 to v2

Signed-off-by: Laurent ARNAL <laurent@clae.net>
2025-09-14 09:05:08 +02:00
David Read 2ccf6bb54d
Update README.md (#19314)
Remove % from condition. It causes an evaluation error.

Signed-off-by: David Read <DavidR1953@users.noreply.github.com>
2025-09-13 10:14:43 +02:00
Holger Friedrich d0efcd78a7
[pythonscripting] Workdaround for failing clean on Windows (#19316)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-12 16:12:20 +02:00
Jacob Laursen deef965aad
[hue] Refactor to use `Stream.toList()` and pattern matching for `instanceof` (#19318)
* Use pattern matching for instanceof

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-09-12 15:49:45 +02:00
Bernd Weymann 073ed7e3f8
[tibber] Tibber pulse hardware support for non tibber customers (#19280)
* subscription handling for non tibber customers

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-09-11 20:18:59 +02:00
tb4jc 424b01ba8c
[modbus.stiebeleltron] Binding enhancements with new Things and support for more (missing) channels (#17962)
* Big change and enhancement of the Stiebel Eltron ISG openHAB binding.

Signed-off-by: Thomas Burri <th@thonojato.ch>
2025-09-11 11:31:36 +02:00
Bernd Weymann 8b0b1f42ec
[tibber] Quarter-hourly prices (#19312)
* adjust price query resolution

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-09-11 10:58:05 +02:00
Marcel Goerentz 5a48ca6b73
[evcc] Fix bug where json parsing could throw an exception (#19302)
* Fix bug where json parsing could throw an exception

Signed-off-by: Marcel Goerentz <57457529+marcelGoerentz@users.noreply.github.com>
2025-09-11 10:53:28 +02:00
Jacob Laursen 4176647972
[netatmo] Refactor `Optional` fields to nullable (#19298)
* Refactor Optional fields to nullable

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-09-11 10:51:15 +02:00
lsiepel 8725638abd
Bump org.mockito:mockito-core from 5.11.0 to 5.19.0 in /bundles (#19283)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.11.0 to 5.19.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.11.0...v5.19.0)
2025-09-10 23:10:30 +02:00
Andrew Fiddian-Green 2808642984
[hue] Add new API support infrastructure (#19309)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
2025-09-10 21:17:05 +02:00
Holger Friedrich beba61421e
Resolve itests after core update (#19311)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-10 21:00:12 +02:00
Markus Michels 3034465f6c
[shelly] Add support for BLU Wallswitch 4, RC Button 4, Remote, Tough ZB, H&T ZB, BLU Distance (#19097)
Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-09-09 22:22:48 +02:00
dependabot[bot] f817cb086a
Bump the ftpupload group with 2 updates (#19300)
Bumps the ftpupload group with 2 updates

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:20:03 +02:00
Дилян Палаузов 0b2e2cf3d1
persistence.jpa/README: add new line (#19301) 2025-09-08 16:18:50 +02:00
Holger Friedrich 43afc5f5f1
Extend dependabot: ftpupload (#19296)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-08 07:30:11 +02:00
dependabot[bot] 4d15e99032
Bump org.eclipse.jdt:ecj from 3.41.0 to 3.43.0 (#19295)
* Bump org.eclipse.jdt:ecj from 3.41.0 to 3.43.0

Bumps [org.eclipse.jdt:ecj](https://github.com/eclipse-jdt/eclipse.jdt.core) from 3.41.0 to 3.43.0.
- [Commits](https://github.com/eclipse-jdt/eclipse.jdt.core/commits)

---
updated-dependencies:
- dependency-name: org.eclipse.jdt:ecj
  dependency-version: 3.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

* Fix null annotation

Also-by: Holger Friedrich <mail@holger-friedrich.de>
Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 07:24:09 +02:00
dependabot[bot] 7c80002e7d
Bump actions/stale from 9 to 10 (#19294)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 07:22:10 +02:00
Roland Tapken a5381d1fed
[serial] Add TCP and binary protocol support (#16205)
* Added code to convert binary data into a hexa-decimal 'charset'

Signed-off-by: Roland Tapken <dev@cybso.de>
2025-09-07 21:59:23 +02:00
lsiepel a5fcfe40c4
[network] Fix discovery performance causing a slow openHAB start (#17972)
* Fix performance
* Improvements
* Add logging
* Improve logging
* Update bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java
* Improve thread handling
* Improve shutdown
* thread cleanup
* Improve per thread allocation
* Stop on finishing all interfaces
* Change Arping to make use of completeablefeature
* Seperate presence detection from lifecycle
* Improve logging and filtering
* Create seperate ScheduledExecutorService
* Fix review comment
* Improve network address checks
* Improve interrupthandling
* Revert threadlocal
* Refactor Presencedetection
* Make LatencyParser accept Windows' <1ms syntax for minimal latency
* Remove misleading reference to non-existing NetworkHandlerBuilder
* Handle thread-safety of NetworkDiscoveryService.executorService
* Fix network interface exclusion
* Make PresenceDetectionValue thread-safe
* Partial refactoring of PresenceDetection

Fixes:
  - Synchronization of lastSeen
  - Joining of async tasks
  - Minor logging improvements
Addition:
  - Create setIcmpPingMethod()

* Partial refactoring of NetworkDiscoveryService

Fixes:
  - Correct the number of addresses in a /24 subnet.
  - Restructure task creation so that one less thread is needed per scanned address, and so that startScan() is guaranteed to return quickly.
  - Create independent threads for posting discovery results, as this can take a long time and might not finish before the executor shuts down.

* Make NetworkHandler thread-safe
* Make SpeedTestHandler thread-safe
* Make sure that process output is consumed
* Implement tool-specific latency parsing
* Fix NetworkDiscoveryService configuration and make the thread pool size configurable
* i18n
* Fix test

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
Co-authored-by: Wouter Born <github@maindrain.net>
Co-authored-by: Ravi Nadahar <nadahar@rediffmail.com>
2025-09-07 19:51:15 +02:00
Holger Friedrich ea276c759a
Adapt add-ons to new hivemq version (#19278)
* Adapt add-ons to new hivemq version

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-07 14:56:48 +02:00
lsiepel 94d7c15e22
[ecovacs] Fix bundle installation by upgrading dependency (#18986)
Also-by: Wouter Born <github@maindrain.net>
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-07 14:45:55 +02:00
lsiepel ef8976982e
[xmppclient] Fix bundle installation by upgrading dependency (#18983)
Also-by: Wouter Born <github@maindrain.net>
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-07 14:44:42 +02:00
lsiepel 458fc9550f
[mqtt.homeassistant] Improve discovery performance (#19201)
* Reduce work within locks
* Fix sorting and stream for performance
* Prevent rescheduling timer
* Reduce memory for two complex lists
* Adjust test
* Fix python packages path on Windows (#19249)

So explicitly add the *nix-style path to the syspath, so that
even on Windows the installed packages can be found.

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
Co-authored-by: Cody Cutrer <cody@cutrer.us>
2025-09-06 11:42:16 +02:00
Martin van Wingerden 280bb38029
[rfxcom] Added additional FirmwareType's (#19286)
Closes: #13024

Signed-off-by: Martin van Wingerden <martin@martinvw.nl>
2025-09-06 11:25:24 +02:00
Bernd Weymann 94e771e4f7
[mercedesme] Bugfix charge program selection (#19272)
* bugfix charge program selection
* reduce logging

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-09-06 10:08:46 +02:00
maniac103 8cf841df14
[homematic] Adjust warning if current value is out of range (#19284)
* [homematic] Handle special values when assembling config

For numeric data points, the central can send us values with special
meaning whose value is outside of the min...max range. Accept such
values while saving configuration.

Signed-off-by: Danny Baumann <dannybaumann@web.de>
2025-09-05 17:27:32 +02:00
Дилян Палаузов 28610e05bc
Remove all super(); calls (#19279)
by calling:
  sed -i "/super();/d" $(git grep -l "super();")
2025-09-05 13:59:34 +02:00
Matthew Skinner ddea03f1e5
Change JSON formatting (#19095)
Signed-off-by: Matthew Skinner <matt@pcmus.com>
2025-09-05 08:17:48 +02:00
lsiepel f3ab7c89a3
[jdbc] Update dependencies (#19172)
* Update mysql to 9.4.0.
* Update hsqldb to 2.7.4
* Update h2 to 2.3.232
* Update mariadb to 3.5.5
* Update postgresql to 42.7.7
* Update sqlite to 3.50.3.0

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-09-03 22:38:43 +02:00
Holger Friedrich 51963af22c
Resolve after upgrade of directory watcher (#19277)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-03 22:18:59 +02:00
Bernd Weymann 46a858191d
[tibber] Add energy price and taxes (#19194)
* add energy price and taxes

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-09-03 20:56:39 +02:00
Dan Cunningham 5d38b6e364
[openhabcloud] Update README with setImage examples (#19239)
* [openhabcloud] Update README with setImage examples

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
2025-09-03 20:42:00 +02:00
Artur-Fedjukevits 539f52715c
[openaitts] Add new voices, model and configuration (#19122)
* update
* Update TTS parameter labels and i18n
* Add default i18n entries for TTS instructions parameter

Signed-off-by: Artur-Fedjukevits <fedjukevitsh@gmail.com>
2025-09-03 00:15:14 +02:00
jsjames c92509de7c
[rollershutterposition] Enhancement to position synchronization and emulation in handlers (#18932)
* Added support for restoreOnStartup of position
Added support to update the state if the binding sends position updates

Signed-off-by: Jeff James <jeff@james-online.com>
2025-09-02 16:15:59 +02:00
Holger Friedrich f2b8f2674c
Resolve after jmdns upgrade (#19274)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-09-01 18:31:52 +02:00
mlobstein 6758592249
Add request timeout (#19271)
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
2025-08-31 22:03:03 +02:00
lsiepel 2bdca5517f
Fix NPE (#18980)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-31 12:05:32 +02:00
Holger Friedrich 9dc6e48174
Resolve after jmdns/xtext/xtend/uom upgrade (#19266)
* Resolve after jmdns upgrade

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-31 11:34:27 +02:00
lsiepel ce80a9861f
[groheondus] Adapt to API changes (#19160)
* Update dependency

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-30 12:28:43 +02:00
dependabot[bot] 5d6409f5d8
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.2 to 3.11.3 (#19179)
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.2 to 3.11.3.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.2...maven-javadoc-plugin-3.11.3)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-version: 3.11.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-30 11:52:23 +02:00
Florian Hotze 8efd3990f8
[jsscripting] Automatically convert event data in UI-based scripts from Java to JS types (#19260)
* [jsscripting] Automatically convert event data in UI-based scripts from Java to JS types

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-30 10:06:55 +02:00
Florian Hotze 2966d33fc1
[fronius] Fix scheme vs schema typo (#19263)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-29 17:57:22 +02:00
Florian Hotze 2f3c291b9d
[jsscripting] Upgrade to openhab-js 5.13.0 (#19259)
Changelog: https://github.com/openhab/openhab-js/blob/main/CHANGELOG.md#5130

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-29 17:43:00 +02:00
Florian Hotze 654be3d89c
[fronius] Use Solar API for getting serial number (#19262)
* [fronius] Use Solar API for getting serial
Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-29 17:42:22 +02:00
Bernd Weymann b4d83ab930
[mercedesme] Add channel energy to max soc (#19243)
* add channel to calculate energy needed to reach max soc

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-08-29 12:44:50 +02:00
Florian Hotze 6ab5d848e8
[froniuswattpilot] Add channels to control surplus SoC & boost charging (#19256)
* [froniuswattpilot] Add channels to control surplus SoC & boost charging

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-29 10:44:07 +02:00
Hans Böhm 887a842c03
change updater logic to reduce serial communications (#19258)
Signed-off-by: Hans Böhm <h.boehm@gmx.at>
2025-08-29 09:08:47 +02:00
Hans Böhm 1c61b66f1d
[comfoair] fix temperatur command calculation (#19257)
* fix temp command

Signed-off-by: Hans Böhm <h.boehm@gmx.at>
2025-08-28 23:13:43 +02:00
Florian Hotze b28715570f
[fronius] Add actions to force battery discharging (#19254)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-27 20:23:34 +02:00
Florian Hotze 4da0fee5b3
[evcc] Remove myself from CODEOWNERS (#19253)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-27 20:13:02 +02:00
Florian Hotze f899b8802b
[jsscripting] Extend auto-injection configuration with transformations & Update default (#19130)
* [jsscripting] Extend auto-injection configuration with transformations

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-27 18:58:15 +02:00
Martin c777ccf38c
[bambulab] Add bambu.sh script to retrieve cloud token (#18896)
* feat(bambulab): Add bambu.sh script to retrieve cloud token

Signed-off-by: Martin Grześlowski <martin.grzeslowski@gmail.com>
2025-08-27 17:49:19 +02:00
maniac103 5e5a0a49af
[ecoflow] Fix labeling of 'supply priority' enum values (#19241)
Fixes #19235

Signed-off-by: Danny Baumann <dannybaumann@web.de>
2025-08-27 17:38:24 +02:00
Cody Cutrer fda5a9ce1a
[mqtt.homeassistant] Fix python packages path on Windows (#19249)
So explicitly add the *nix-style path to the syspath, so that
even on Windows the installed packages can be found.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
2025-08-27 09:19:48 +02:00
Markus Michels 0e37ae1ed2
[shelly] Fix script upload - don't delete Non-OH scripts (#19231)
Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-08-26 21:18:40 +02:00
MSc c85037e10c
reduce log level for homematic device type conversions to DEBUG (#19237)
chore: reduce the log level for homematic device type conversions to DEBUG because this message is repeatedly logged and is therefore too noisy for the INFO level

Signed-off-by: Markus Schuch <markus_schuch@web.de>
2025-08-25 06:58:28 +02:00
Paul Smedley b10918c91f
[tuya] Add Singapore Data Centre (#19233)
Signed-off-by: Paul Smedley <paul@smedley.id.au>
2025-08-24 23:03:56 +02:00
openhab-bot 15bca863ef
New translations shelly.properties (German) (#19236) 2025-08-24 21:09:56 +02:00
Dan Cunningham 4bf7372892
[matter] Fixes Label regression in bridge (#19238)
Custom labels were being ignored due to a regression from my last PR.

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
2025-08-24 21:07:13 +02:00
dependabot[bot] 093a80607d
Bump actions/setup-java from 4 to 5 (#19221)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-24 18:52:49 +02:00
Holger Friedrich c698d0c5ae
Resolve itests after hivemq upgrade (#19232)
Upgrades netty from 4.1.118 to 4.1.122

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-24 15:54:17 +02:00
openhab-bot c417fe3f2f
New translations sungrow.properties (Italian) (#19203) 2025-08-23 17:10:11 +02:00
Marcel Goerentz 12b1757874
[evcc] Bugfix channels not getting updated (#19220)
* Fix bug #19206 and add datapoints for currents for grid and chargers

Signed-off-by: Marcel Goerentz <57457529+marcelGoerentz@users.noreply.github.com>
2025-08-23 10:31:17 +02:00
Markus Michels 761836dca3
[shelly] Refactor BLU support (#19170)
* Refactoring BLU support as basis for adding new BLU device types

* Completely refactored, supports decoding of all BTHome sensor id and is
the basis to add more BLU devices by an upcoming PR.

Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-08-22 19:51:41 +02:00
Holger Friedrich d748f32c65
Bump org.eclipse.jdt:ecj from 3.39.0 to 3.41.0 (#19215)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-22 18:18:57 +02:00
lsiepel 687e283ff3
Fix type (#19212)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-21 22:38:10 +02:00
Wouter Born 7a158761e6
Revert "Bump org.eclipse.jdt:ecj from 3.39.0 to 3.42.0 (#19180)" (#19213)
This reverts commit 0d36961494.
2025-08-21 21:11:29 +02:00
Marcel Goerentz f8fed2b938
[evcc] Fix bug #19206 where heating devices showing % instead of °C (#19211)
* Fix bug #19206 and add datapoints for currents for grid and chargers

Signed-off-by: Marcel Goerentz <57457529+marcelGoerentz@users.noreply.github.com>
2025-08-21 15:36:51 +02:00
Carsten Mogge 1c5f750e97
Update README.md (#19208)
* Update README.md

just a few corrections

Signed-off-by: Carsten Mogge <carsten.mogge@gmail.com>
2025-08-21 11:20:48 +02:00
maniac103 43e804f179
[ecovacs] Rename 'autoEmpty' state to 'emptying' (#19210)
Makes it consistent with 'drying', 'washing' and 'returning' states.

Signed-off-by: Danny Baumann <dannybaumann@web.de>
2025-08-21 11:19:13 +02:00
Holger Friedrich 03058507be
Upgrade cxf to 3.6.8 (#19136)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-20 15:43:21 +02:00
Holger Friedrich 0b45e88932
Resolve itests after hivemq upgrade (#19178)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-20 06:34:01 +02:00
lsiepel 5c1762d19c
[amazoneechocontrol] Adapt to changed API (#19182)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-19 14:53:17 +02:00
lsiepel c7da9f4216
Increase message size (#19196)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-19 12:33:10 +02:00
dependabot[bot] 0d36961494
Bump org.eclipse.jdt:ecj from 3.39.0 to 3.42.0 (#19180)
Bumps [org.eclipse.jdt:ecj](https://github.com/eclipse-jdt/eclipse.jdt.core) from 3.39.0 to 3.42.0.
- [Commits](https://github.com/eclipse-jdt/eclipse.jdt.core/commits)

---
updated-dependencies:
- dependency-name: org.eclipse.jdt:ecj
  dependency-version: 3.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 09:02:36 +02:00
David Pace c9d3bb7a2f
[boschshc] Fix unit test setups (part 2) (#19193)
- use `mock()` instead of `@Mock` in LongPollingTest

Signed-off-by: David Pace <dev@davidpace.de>
2025-08-18 23:12:28 +02:00
Sönke Küper 25160cb40f
[sungrow] Added configuration option for modbus read retries (#19186)
* Added configuration option for modbus retries.

Signed-off-by: Sönke Küper <soenkekueper@gmx.de>
2025-08-18 21:05:36 +02:00
Bernd Weymann 5bab3f4941
[mercedesme] Improve HTTP 429 handling and implement new authorization flow (#19099)
Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-08-18 20:20:25 +02:00
openhab-bot a2a637c305
New translations sonos.properties (Italian) (#19192) 2025-08-18 19:59:18 +02:00
Carsten Mogge 441bf584a7
[bambulab] Update README.md (#19184)
* Update README.md

Added a description for local use of the Bambu printer (not via cloud)

Signed-off-by: Carsten Mogge <carsten.mogge@gmail.com>
2025-08-18 19:02:03 +02:00
Dan Cunningham 1aa39b743a
[matter] General Updates (#19112)
* [matter] General Updates

Refactors Typescript bridge logic (behaviors)
Adds better bridge item state handling (loop prevention)
Add "Mode Select Device" as bridge device
Better error handling of nodejs runtime
Fixes small NPE issues with missing attributed
Improves generated cluster classes
Updates to latest matter.js stable
README improvements

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
2025-08-18 15:48:18 +02:00
Jacob Laursen fa5ee6a6ba
Simplify tests (#19187)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-08-18 10:34:58 +02:00
lsiepel 595655f19c
Fix pom binding name (#19189)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-18 08:05:49 +02:00
David Pace 71342e3df8
[boschshc] Fix unit test setups (#19188)
- do not mix mocking via `@Mock` annotation and `mock()` method
- add `@ExtendWith(MockitoExtension.class)` to classes extending test
classes that use `@Mock` annotations

Signed-off-by: David Pace <dev@davidpace.de>
2025-08-17 23:58:07 +02:00
mlobstein befdc91969
[tasmotaplug] Add PulseTime channels (#19161)
* Add PulseTime channels

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
2025-08-17 22:39:57 +02:00
Holger Friedrich d982d4d1da
[transform.bin2json] Upgrade jbbp to 3.0.1 (#19175)
* Upgrade jbbp from 1.4.1 to 3.0.1,
  Changelog: https://github.com/raydac/java-binary-block-parser?tab=readme-ov-fie#change-log

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-17 21:43:06 +02:00
Plamen K. Kosseff cdbac5cc52
[Shelly] Fix support for "Shelly AZ Plug" device model S3PL-10112EU (#19177)
Signed-off-by: Plamen K. Kosseff <p.kosseff@gmail.com>
2025-08-17 20:50:51 +02:00
lsiepel 3c075cbb2d
Improve meaningful links (#19155)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-17 15:25:38 +02:00
Holger Friedrich a190fe1e96
Add ecj to Dependabot config (#19176)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-17 10:26:57 +02:00
Nadahar cfea7651fc
[exec] Prevent deadlock (#19152)
* Add TextOutputConsumer
* Add stdout and stderr channels
* Add charset configuration
* Add upgrade instructions

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
2025-08-17 08:25:23 +02:00
lolodomo 02e7b1c252
[sonos] Enhance logging for playback of notifications (#19168)
Related to #19158

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
2025-08-16 12:38:06 +02:00
joschkopf 3b058caeb9
[lgwebos] Add support for wakeonlan in docker (#19024)
* Add support for wakeonlan in docker

When running in docker, broadcast address can be configured to allow the wol packet to be forwarded to the correct network.

Signed-off-by: Jochen Rueter <jo.rueter@gmail.com>
Co-authored-by: lsiepel <leosiepel@gmail.com>
2025-08-16 12:23:52 +02:00
Holger Friedrich 5bb7ef59c0
[knx] Refine default semantic tags (#19031)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-16 11:55:38 +02:00
Wouter Born 0c6a72df58
Remove jackson-datatype-jdk8 bundle from Jinja Transformation feature (#19114)
To prevent upgrade issues it was added to the Jackson feature in openHAB Core.

Signed-off-by: Wouter Born <github@maindrain.net>
2025-08-16 11:45:09 +02:00
Andrew Fiddian-Green f54ff70aaf
[growatt] Describe Grott installation via openHABian (#19169)
* describe openHABian installation
* minor improvements

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
2025-08-16 10:38:16 +02:00
Cody Cutrer 07b11edbbd
[tuya] remove dependency on jose4j (#19171)
Signed-off-by: Cody Cutrer <cody@cutrer.us>
2025-08-15 23:04:24 +02:00
Marcel Goerentz 605e9ba668
Update EvccSiteHandler.java (#19165)
Hotfixing bug where smartCostType was tried to set even it was null

Signed-off-by: Marcel Goerentz <57457529+marcelGoerentz@users.noreply.github.com>
2025-08-15 12:58:30 +02:00
dependabot[bot] 05aa010ad8
Bump actions/checkout from 4 to 5 (#19164)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-15 10:32:53 +02:00
lolodomo 8cae3d5676
[Sonos] Add support for Sonos Arc Ultra (#19162)
Closes #19075

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
2025-08-15 10:24:22 +02:00
Andrew Fiddian-Green e65acf1534
[avmfritz] Fix boost and window open modes (#19118)
* fix boost and window open modes

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
2025-08-15 00:03:19 +02:00
Markus Michels 2a1ee509e9
[shelly] Add support for Shelly Pro 3EM-3CT63 and Pro 3EM-400 (#18952)
Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-08-14 21:34:32 +02:00
lsiepel 787242d982
Fix (#19141)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-14 11:46:47 +02:00
lsiepel 4d403ea438
[somfytahoma]] Correct `exteriorheatingsystem` label (#19156)
* Update i18n file

Signed-off-by: Leo Siepel <leosiepel@gmail.com>

* Limit request

Signed-off-by: Leo Siepel <leosiepel@gmail.com>

* Cleanup

Signed-off-by: Leo Siepel <leosiepel@gmail.com>

---------

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-13 20:28:23 +02:00
Marcel Goerentz 24747129e6
[evcc] Rework the binding, add missing datapoints and make it future ready (#18946)
* First commit of reworked binding

Signed-off-by: Marcel Goerentz <m.goerentz@t-online.de>
2025-08-13 20:15:32 +02:00
Markus Michels 0d28575470
Add support for Shelly Pro 1UL and Pro 1PMUL (#19137)
Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-08-13 08:14:51 +02:00
jimtng ce138b5ffd
[jrubyscripting] Upgrade to JRuby 10.0.2.0 (#19151)
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-08-12 17:09:57 +02:00
Christian Kemper f871501135
[tibber] Add support for power consumption and production in one channel (#19124)
* [tibber] Add support for power consumption and production in one channel

Signed-off-by: Christian Kemper <dev@bestof5.de>
2025-08-12 13:07:59 +02:00
Andrew Fiddian-Green 4cdd798205
[smartmeter] Implement AbstractStorageBasedTypeProvider (#19146)
* Add StorageBasedTypeProvider

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
2025-08-11 22:02:56 +02:00
David Goodyear 3b9017bfb0
[electroluxappliance] Improve JWT handling (#19021)
* [electroluxappliance] Readme.md corrections

Signed-off-by: David Goodyear <david.goodyear@gmail.com>
2025-08-11 12:35:53 +02:00
lo92fr 866bc99a31
fix userInfo url changes (fix issue https://github.com/openhab/openhab-addons/issues/19140). (#19142)
Signed-off-by: Laurent ARNAL <laurent@clae.net>
2025-08-11 12:23:11 +02:00
Nadahar 3e66c13f0e
Make dedicated thread pools for Exec and Chromecast bindings (#19133)
* Make dedicated thread pools for Exec and Chromecast bindings

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
2025-08-11 12:18:06 +02:00
neutmute 5181b7df1b
[mqtt.awtrixlight] Improve documentation (#19135)
* Things and Items fixes

Fix some errors in the docs

Signed-off-by: neutmute <neutmute@users.noreply.github.com>
2025-08-11 11:48:20 +02:00
Paul Smedley 46c60091d3
Fix typo (#19139)
Signed-off-by: Paul Smedley <paul@smedley.id.au>
2025-08-11 11:34:28 +02:00
openhab-bot f307850a10
New translations shelly.properties (German) (#19120) 2025-08-10 21:47:27 +02:00
Holger Friedrich 8ae35c0c84
Upgrade Karaf from 4.4.7 to 4.4.8 (#19076)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-08-09 21:27:48 +02:00
lsiepel 8ebd608060
Possible fix comparison (#18975)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-08-09 17:12:33 +02:00
Florian Hotze 7433801d22
[jsscripting] Fix memory leak caused by GraalJSScriptEngine not closed properly (#18226)
* [jsscripting] Close GraalJSScriptEngine when closing OpenhabGraalJSScriptEngine

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-09 13:18:18 +02:00
Christian Kemper f9f2a24191
[evcc] Simple Fix for #19116 (#19134)
Signed-off-by: christian <dev@bestof5.de>
Co-authored-by: Christian Kemper <dev@bestof5.de>
2025-08-09 09:56:48 +02:00
Florian Hotze 58973f9cf2
[jsscripting] Fix wrapping UI scripts fails if comment in last line (#19129)
Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-09 07:55:15 +02:00
jimtng 830db535f4
[mqtt.generic] Adjust unit description to avoid literal input of quoted examples (#19119)
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-08-07 09:25:57 +02:00
Christian Kemper 09c59ac415
[tibber] Add support for average channel (#19111)
Signed-off-by: Christian Kemper <dev@bestof5.de>
2025-08-06 11:14:51 +02:00
Markus Michels 294cfc4963
[shelly] Refactor device definitions (#19093)
* Move all device / thing related definitoons to class ShellyDevices.
Device Profile initialization is now based THING_TYPE_CLASSES, some unit
tests fixed.

* The BLU Gateway is a regular W-LAN device, has the Gen 2 API and acts as
a gateway for the BLU devices. BLUGWxx was renamed to PLUSBLUGWxx to
make it clear and moved the declarations under the the Shelly Plus
series, removed it from the BLU_SENSOR group

* group GROUP_MINI_THING_TYPES added, group GROUP_LIGHT_THING_TYPES is
build from sub-groups, SUPPORTED_THING_TYPES uses GROUP_xxx and adds
only remaining thing types to improve maintainability

Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-08-06 11:12:51 +02:00
Дилян Палаузов b3b1cbc6d7
[Groovy] Text about alternative, manual installation is outdated (#19115) 2025-08-05 19:19:55 +02:00
openhab-bot 83c1182889
New translations ecovacs.properties (Italian) (#19108) 2025-08-04 23:10:37 +02:00
Jeremy 632e15b227
[insteon] Clear auto-unboxing null pointer warnings (#19107)
Signed-off-by: Jeremy Setton <jeremy.setton@gmail.com>
2025-08-04 19:53:35 +02:00
Patrick Weniger 173c7600d9
[surepetcare] Fix hubRssi `NullPointerException` (#19106)
* [surepetcare] Fix NullPointerException when signal values are null

Signed-off-by: Patrick Weniger <patrickweniger@gmx.de>
2025-08-03 16:57:30 +02:00
Дилян Палаузов 5ccd1859e3
[groovyscripting] Upgrade Groovy to 4.0.28 (#18988) 2025-08-03 16:06:52 +02:00
jimtng b57b1caf60
[ecovacs] Add Deebot T30 (PRO) OMNI and support Scenario Cleaning (#17162)
* [ecovacs] Add Deebot T30 (PRO) OMNI

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
2025-08-03 14:04:28 +02:00
Ondrej Pecta a358bf83dd
[somfytahoma] make token refreshing independent on event polling frequency (#19038)
Signed-off-by: Ondrej Pecta <opecta@gmail.com>
2025-08-03 11:26:19 +02:00
Wouter Born 442589cafe
Update Jackson to 2.19.2 (#19100)
Signed-off-by: Wouter Born <github@maindrain.net>
2025-08-03 10:41:55 +02:00
lolodomo d14f74b9e9
[sonos] Ignore Sonos Boost and any Sub including Sonos Sub 4 (#19103)
Related to #19075

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
2025-08-03 10:28:48 +02:00
Paul Smedley 533ed4c18f
Possible fix for passwords with special characters (#19102)
Signed-off-by: Paul Smedley <paul@smedley.id.au>
2025-08-03 08:14:13 +02:00
dependabot[bot] 2cb32ae44c
Bump org.codehaus.mojo:exec-maven-plugin from 3.1.0 to 3.5.1 (#19050)
Bumps [org.codehaus.mojo:exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) from 3.1.0 to 3.5.1.
- [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/exec-maven-plugin-3.1.0...3.5.1)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:exec-maven-plugin
  dependency-version: 3.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 19:58:05 +02:00
Florian Hotze e47e6dea8e
[froniuswattpilot] Upgrade wattpilot4j to 2.0.0 (#19092)
This new version brings no code changes,
it only moves the library to a new namespace.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-08-01 17:35:58 +02:00
Jacob Laursen eef2ed1ea3
[bluetooth.bluez] Fix some warnings (#19085)
* Remove unused method

Left-over from #8970

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-31 08:28:37 +02:00
Jacob Laursen 093d4948f0
[bluetooth.bluez] Fix data not being updated (#19086)
* Fix ServiceDataEvent

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-31 08:25:30 +02:00
Jacob Laursen 3725576ade
Fix duplicate channel (#19082)
Fixes #19080

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-29 22:18:48 +02:00
matmai 976a4acd75
[shelly] Add initial support for Shelly Dimmer Gen3 (#18686)
Signed-off-by: Matthias Maier <code@telebim.de>
2025-07-29 12:31:42 +02:00
Jacob Laursen 1e13bef624
[bluetooth.bluez] Upgrade bluez-dbus to 0.3.2 (#19074)
* Upgrade bluez-dbus to 0.3.2
* Simplify code

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-29 11:38:07 +02:00
Wouter Born fe5414ff5c
Use Maven 3.9.11 with wrapper and add checksum verification (#19073)
- Set Maven version to 3.9.11 in .mvn/wrapper/maven-wrapper.properties
- Set SHA-256 checksum for wrapper JAR to ensure integrity

Signed-off-by: Wouter Born <github@maindrain.net>
2025-07-28 21:54:56 +02:00
Jacob Laursen 929a367e7b
Fix name (#19072)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-28 19:24:45 +02:00
Jacob Laursen 8bab650303
[bluetooth.bluez] Fix data not being updated (#19071)
* Fix CharacteristicUpdateEvent

Resolves #19070

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-28 19:21:00 +02:00
Jacob Laursen 0180b9437b
Refactor Optional to nullable (#19061)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-27 18:21:22 +02:00
Wouter Born 4d0a04aa87
Use Maven wrapper with GHA (#19067)
Signed-off-by: Wouter Born <github@maindrain.net>
2025-07-27 18:19:31 +02:00
MikeTheTux 5acfef3f97
[automower] Status update via Husqvarna WebSocket API (#18630)
* initial checkin: working WSS communication

Signed-off-by: Michael Weger <weger.michael@gmx.net>
2025-07-27 15:02:17 +02:00
dependabot[bot] 0d1dcd6133
Bump the jacoco group with 2 updates (#19065)
Bumps the jacoco group with 2 updates: [org.jacoco:org.jacoco.agent](https://github.com/jacoco/jacoco) and [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco).


Updates `org.jacoco:org.jacoco.agent` from 0.8.12 to 0.8.13
- [Release notes](https://github.com/jacoco/jacoco/releases)
- [Commits](https://github.com/jacoco/jacoco/compare/v0.8.12...v0.8.13)

Updates `org.jacoco:jacoco-maven-plugin` from 0.8.12 to 0.8.13
- [Release notes](https://github.com/jacoco/jacoco/releases)
- [Commits](https://github.com/jacoco/jacoco/compare/v0.8.12...v0.8.13)

---
updated-dependencies:
- dependency-name: org.jacoco:org.jacoco.agent
  dependency-version: 0.8.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: jacoco
- dependency-name: org.jacoco:jacoco-maven-plugin
  dependency-version: 0.8.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: jacoco
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-27 12:15:00 +02:00
Jacob Laursen 3fc91e5e92
[freeboxos] Fix Markdown (#19062)
* Fix Markdown

Related to #19010

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-27 12:07:42 +02:00
Holger Friedrich 26450bc6fc
Dependabot groups (#19063)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-07-27 10:53:00 +02:00
Wouter Born cf671c31b0
Minor README improvements (#19064)
* Repository contains more than just bindings
* Update i18n-maven-plugin version
* Uppercase Maven

Signed-off-by: Wouter Born <github@maindrain.net>
2025-07-27 09:40:39 +01:00
lsiepel 45709e089c
Fix markdown bindings T-V (#19028)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 21:36:00 +02:00
lsiepel 8eda464a0e
Fix markdown io / persistence / transform / voice (#19034)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:59:28 +02:00
lsiepel 0d76f68c96
Fix markdown bindings W-Z (#19032)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:58:57 +02:00
lsiepel 4eb6bad3be
Fix markdown bindings Q-S (#19027)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:57:51 +02:00
lsiepel 8b26f7d3bd
Fix markdown bindings O-P (#19026)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:57:22 +02:00
lsiepel 0421d6e8f9
Fix markdown bindings M-N (#19025)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:56:53 +02:00
lsiepel 04d8143026
Fix markdown bindings G-H (#19016)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:56:20 +02:00
lsiepel 834b587623
Fix markdown bindings K-L (#19018)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:55:53 +02:00
lsiepel 5c817ea190
Fix markdown bindings I-J (#19017)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:55:24 +02:00
lsiepel 12ea895db2
Fix markdown bindings E-F (#19015)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:54:54 +02:00
lsiepel a4f2c1546e
Fix markdown bindings C-D (#19013)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:54:27 +02:00
lsiepel 9440628914
Fix markdown bindings A-B (#19012)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 20:53:55 +02:00
lsiepel b8721e7f64
[various] Fix ConstantNameCheck (#18992)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 16:14:58 +02:00
openhab-bot ffbaad93d7
New Crowdin updates (#19035)
* New translations avmfritz.properties (German)

* New translations shelly.properties (German)
2025-07-26 13:17:38 +02:00
lsiepel 901927d0a2
Fix markdown repo files (#19033)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 12:13:39 +02:00
lsiepel 6304dbc7b6
Freebox removal (#19000)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-26 11:17:00 +02:00
Ondrej Pecta cc45e8f31e
[orbitbhyve] fix inability to set rain delay (#19056)
Signed-off-by: Ondrej Pecta <opecta@gmail.com>
2025-07-26 09:28:40 +02:00
Markus Michels bdcde5a1ef
[shelly] Use thing type `shellyplusht` for Shelly Plus HT Gen3 (#18943)
* Thing type shellyplushtg3 removed, instead it will be mapped to
shellyplusht

Signed-off-by: Markus Michels <markus7017@gmail.com>
2025-07-26 09:06:41 +02:00
Florian Hotze c85837c371
[jsscripting] Upgrade openhab-js to 5.12.0 (#19054)
Changelog: https://github.com/openhab/openhab-js/blob/main/CHANGELOG.md#5120.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-07-25 13:08:51 +02:00
dependabot[bot] 6603704bc9
Bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.2 to 3.5.3 (#19047)
Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.2...surefire-3.5.3)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 13:00:42 +02:00
dependabot[bot] 090d1e13a2
Bump org.apache.maven.plugins:maven-deploy-plugin from 3.1.3 to 3.1.4 (#19043)
Bumps [org.apache.maven.plugins:maven-deploy-plugin](https://github.com/apache/maven-deploy-plugin) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/apache/maven-deploy-plugin/releases)
- [Commits](https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.1.3...maven-deploy-plugin-3.1.4)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-deploy-plugin
  dependency-version: 3.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 12:59:38 +02:00
dependabot[bot] 4fc4b35d99
Bump org.apache.maven.plugins:maven-install-plugin from 3.1.3 to 3.1.4 (#19051)
Bumps [org.apache.maven.plugins:maven-install-plugin](https://github.com/apache/maven-install-plugin) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/apache/maven-install-plugin/releases)
- [Commits](https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.1.3...maven-install-plugin-3.1.4)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-install-plugin
  dependency-version: 3.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:34:19 +02:00
dependabot[bot] a3f8c72bef
Bump org.codehaus.mojo:build-helper-maven-plugin from 3.6.0 to 3.6.1 (#19041)
Bumps [org.codehaus.mojo:build-helper-maven-plugin](https://github.com/mojohaus/build-helper-maven-plugin) from 3.6.0 to 3.6.1.
- [Release notes](https://github.com/mojohaus/build-helper-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/build-helper-maven-plugin/compare/3.6.0...3.6.1)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:build-helper-maven-plugin
  dependency-version: 3.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:32:31 +02:00
dependabot[bot] 73c92070e3
Bump org.apache.maven.plugins:maven-clean-plugin from 3.4.0 to 3.5.0 (#19048)
Bumps [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/apache/maven-clean-plugin/releases)
- [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.4.0...maven-clean-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-clean-plugin
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:31:57 +02:00
dependabot[bot] 6349db2df3
Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.5.0 to 3.6.1 (#19044)
Bumps [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.5.0 to 3.6.1.
- [Release notes](https://github.com/apache/maven-enforcer/releases)
- [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.5.0...enforcer-3.6.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-enforcer-plugin
  dependency-version: 3.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:31:13 +02:00
dependabot[bot] 280a6db9e8
Bump com.mycila:license-maven-plugin from 4.6 to 5.0.0 (#19040)
Bumps [com.mycila:license-maven-plugin](https://github.com/mathieucarbou/license-maven-plugin) from 4.6 to 5.0.0.
- [Release notes](https://github.com/mathieucarbou/license-maven-plugin/releases)
- [Commits](https://github.com/mathieucarbou/license-maven-plugin/compare/license-maven-plugin-4.6...v5.0.0)

---
updated-dependencies:
- dependency-name: com.mycila:license-maven-plugin
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:30:22 +02:00
dependabot[bot] a468112ad5
Bump org.apache.maven.plugins:maven-compiler-plugin (#19042)
Bumps [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.13.0 to 3.14.0.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.13.0...maven-compiler-plugin-3.14.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-version: 3.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:29:12 +02:00
lsiepel a8ce93f4d5
[zwavejs] Fix Channel configuration overwrite (#19036)
* Rewrite updateChannels

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-25 11:13:34 +02:00
dependabot[bot] cadb8cb298
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.2 to 3.5.3 (#19045)
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.2...surefire-3.5.3)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
  dependency-version: 3.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 10:31:06 +02:00
Holger Friedrich f34491551d
Dependabot for Plugins (#19039)
* Dependabot for Plugins

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
Co-authored-by: lsiepel <leosiepel@gmail.com>
2025-07-25 10:08:56 +02:00
lsiepel 9189ca5c4e
[zwavejs] Fix humidity unit detection (#19003)
* Fix humidity unit
* Improve check

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
Signed-off-by: lsiepel <leosiepel@gmail.com>
2025-07-24 10:45:49 +02:00
Holger Friedrich 8fa366d119
Enable Dependabot PRs (#19029)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
2025-07-24 01:33:34 +02:00
Christoph Weitkamp 6c45f08eca
Retire as a codeowner for hue (#19023)
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
2025-07-23 22:25:26 +02:00
Florian Hotze 18d288b679
[jsscripting] Wrap UI scripts, make dependency tracking configurable & config refactorings (#19019)
* [jsscripting] Add engineIdentifier to logging on OpenhabGraalJSScriptEngine

Signed-off-by: Florian Hotze <dev@florianhotze.com>
2025-07-23 22:14:32 +02:00
Jacob Laursen e458413289
Fix zone 3 input source update (#19022)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-07-23 01:12:54 +02:00
Christoph Weitkamp 1b89ea87fb
[avmfritz] Fix links in documention (#19014)
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
2025-07-22 18:37:08 +02:00
lsiepel 521a1da093
Remaining runbundles (#19009)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-22 11:25:48 +02:00
Kai Kreuzer e6bd19f0f2
Fix runbundles on astro itests (#19008)
Signed-off-by: Kai Kreuzer <kai@openhab.org>
2025-07-22 09:56:00 +02:00
Zhivka Dimova cd139da43b
[enocean] D2-01-0C implement support for pilot wire mode (#17450)
* [enocean] fix typos

Rename constants name from *RESPONE to *RESPONSE

Signed-off-by: Zhivka Dimova <zhivka.dimova@myforest.net>
2025-07-22 00:47:19 +02:00
Bernd Weymann ffc5ba6e46
[mspa] Initial contribution (#18746)
* initial version

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-07-22 00:44:40 +02:00
MikeTheTux 445c17891b
[ondilo] Add new Channels and Properties (#18978)
* increased buffer to 3min
fixed error with assignment to local instead of global variable

Signed-off-by: Michael Weger <weger.michael@gmx.net>
2025-07-22 00:42:06 +02:00
Bernd Weymann e8b5839c5d
[mercedesme] internal websocket rework (#18984)
* version update, remove private discovery service reg

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
2025-07-22 00:39:38 +02:00
Matthew Skinner 1d8f6f4e89
[ipcamera] Add missing ONVIF event topics for TP Link Tapo cameras. (#18996)
* Add missing ONVIF topics for TP Link Tapo cameras.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
2025-07-22 00:38:24 +02:00
Aron Beurskens bf85797032
* Better handling of `server` in configuration. Now possible to add hostname which can be matched against plex configuration (#18997)
* Updated error handling which not supports `CONFIGURATION_ERROR`
Signed-off-by: Aron Beurskens <aron@beurskens.nl>
2025-07-22 00:37:55 +02:00
lsiepel ce67b0145e
[various] Fix LocalVariableNameCheck (#18994)
* Fix LocalVariableNameCheck

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-22 00:00:12 +02:00
lsiepel 6592ccaa18
Fix StaticVariableNameCheck (#18993)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-21 23:58:29 +02:00
lsiepel 7e3b22cd78
[various] Fix sat warnings (#18991)
* Fix NeedBracesCheck
* Fix  LocalFinalVariableNameCheck
* Fix ModifierOrderCheck
* Fix EqualsAvoidNullCheck
* Fix AvoidStarImportCheck
* Fix AuthorContribution

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-21 23:57:33 +02:00
lsiepel d64a011ab4
Fix NoEmptyLineSeparatorCheck (#18990)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-21 23:54:54 +02:00
lsiepel 446c8c089f
Resolve runbundles (#19004)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-21 23:41:04 +02:00
Wouter Born 5d0339ab09
Update OH version in skeleton scripts (#19005)
Signed-off-by: Wouter Born <github@maindrain.net>
2025-07-21 21:29:16 +02:00
lsiepel 90b3d12aa3
Spotless after release (#19002)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
2025-07-21 20:21:58 +02:00
openhab-bot d779c17335 [unleash-maven-plugin] Preparation for next development cycle. 2025-07-21 09:29:14 +00:00
1892 changed files with 44516 additions and 23374 deletions

52
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,52 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference
version: 2
# this would enable querying all packages from our repo first,
# disabled for now as we do not want to use it. Kept for reference.
#registries:
# openhab-jfrog:
# type: "maven-repository"
# url: "https://openhab.jfrog.io/artifactory/libs-all/"
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- dependencies
- package-ecosystem: "maven" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
open-pull-requests-limit: 15
labels:
- dependencies
groups:
jacoco:
patterns:
- "*jacoco*"
ftpupload:
patterns:
- "org.apache.ftpserver:*"
- "*mina-core*"
allow:
- dependency-name: "org.apache.maven.plugins:*"
- dependency-name: "org.codehaus.mojo:*"
- dependency-name: "*jacoco*" # code coverage tool, both tool and plugin
- dependency-name: "*plugin*"
- dependency-name: "org.eclipse.jdt:ecj" # eclipse compiler
- dependency-name: "org.apache.ftpserver:*" # ftpserver
- dependency-name: "org.apache.mina:mina-core" # ftpserver
ignore:
- dependency-name: "com.github.jaxb-xew-plugin:jaxb-xew-plugin" # updated with core
- dependency-name: "com.diffplug.spotless:spotless-maven-plugin" # updated together with SAT
- dependency-name: "*graalvm*" # do not touch graalvm related stuff
- dependency-name: "org.openapitools:openapi*" # updated togeter with openapi generator
- dependency-name: "org.apache.openjpa:openjpa*" # should be done with openjpa upgrades"

View File

@ -18,7 +18,7 @@ function mvnp() {
set -o pipefail # exit build with error when pipes fail
local reactor_size=$(find -name "pom.xml" | grep -vE '/src/|/target/' | wc -l)
local padding=$(bc -l <<< "scale=0;2*(l($reactor_size)/l(10)+1)")
local command=(mvn $@)
local command=(./mvnw $@)
exec "${command[@]}" 2>&1 | # execute, redirect stderr to stdout
tee "$BUILD_LOG" | # write output to log
stdbuf -oL grep -aE '^\[INFO\] Building .+ \[.+\]$' | # filter progress
@ -30,7 +30,7 @@ function build_all() {
echo
echo "Building all projects"
echo
echo "+ mvn $ARGUMENTS"
echo "+ ./mvnw $ARGUMENTS"
echo
mvnp $ARGUMENTS
@ -79,16 +79,16 @@ function addon_projects() {
function build_addon() {
local addon="$1"
local mvn_command="mvn $ARGUMENTS -am -amd -pl $(addon_projects $addon)"
local command="./mvnw $ARGUMENTS -am -amd -pl $(addon_projects $addon)"
echo
echo "Building add-on: $addon"
echo
echo "+ $mvn_command"
echo "+ $command"
echo
set -o pipefail # exit build with error when pipes fail
$mvn_command 2>&1 | tee "$BUILD_LOG"
$command 2>&1 | tee "$BUILD_LOG"
exit $?
}
@ -104,5 +104,5 @@ function build_based_on_changes() {
fi
}
mvn -v
./mvnw -v
build_based_on_changes

View File

@ -19,7 +19,6 @@ jobs:
fail-fast: false
matrix:
java: [ '21' ]
maven: [ '3.9.10' ]
os: [ 'ubuntu-24.04' ]
name: Build (Java ${{ matrix.java }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
@ -28,11 +27,11 @@ jobs:
steps:
- name: Checkout
if: github.head_ref == ''
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Checkout merge
if: github.head_ref != ''
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge
@ -47,17 +46,11 @@ jobs:
${{ runner.os }}-maven-
- name: Set up Java ${{ matrix.java }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
# pinning to SHA to mitigate possible supply chain attacks
- name: Set up Maven ${{ matrix.maven }}
uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5
with:
maven-version: ${{ matrix.maven }}
- name: Register Problem Matchers
if: ${{ matrix.java == '21' }}
id: problem_matchers

View File

@ -51,13 +51,13 @@ jobs:
- name: Checkout
if: ${{ github.head_ref == '' && env.LABEL == 'rebuild' }}
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Checkout merge
if: ${{ github.head_ref != '' && env.LABEL == 'rebuild' }}
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge
persist-credentials: false

View File

@ -13,7 +13,7 @@ jobs:
issues: write
steps:
- name: Stale issues check
uses: actions/stale@v9
uses: actions/stale@v10
with:
days-before-issue-stale: 60
days-before-issue-close: 180

20
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
distributionSha256Sum=0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb

View File

@ -119,7 +119,7 @@
/bundles/org.openhab.binding.ephemeris/ @clinique
/bundles/org.openhab.binding.epsonprojector/ @mlobstein
/bundles/org.openhab.binding.etherrain/ @dfad1469
/bundles/org.openhab.binding.evcc/ @florian-h05
/bundles/org.openhab.binding.evcc/ @marcelGoerentz
/bundles/org.openhab.binding.evohome/ @Nebula83
/bundles/org.openhab.binding.exec/ @kgoderis
/bundles/org.openhab.binding.feed/ @openhab/add-ons-maintainers
@ -133,7 +133,6 @@
/bundles/org.openhab.binding.folding/ @fa2k
/bundles/org.openhab.binding.foobot/ @airboxlab @Hilbrand
/bundles/org.openhab.binding.freeathome/ @andrasU
/bundles/org.openhab.binding.freebox/ @lolodomo
/bundles/org.openhab.binding.freeboxos/ @clinique
/bundles/org.openhab.binding.freecurrency/ @J-N-K
/bundles/org.openhab.binding.frenchgovtenergydata/ @clinique
@ -170,7 +169,7 @@
/bundles/org.openhab.binding.homewizard/ @Daniel-42
/bundles/org.openhab.binding.hpprinter/ @cossey
/bundles/org.openhab.binding.http/ @J-N-K
/bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg
/bundles/org.openhab.binding.hue/ @andrewfg
/bundles/org.openhab.binding.huesync/ @pgfeller
/bundles/org.openhab.binding.hydrawise/ @digitaldan
/bundles/org.openhab.binding.hyperion/ @tavalin
@ -261,6 +260,7 @@
/bundles/org.openhab.binding.mqtt.homeassistant/ @antroids @ccutrer
/bundles/org.openhab.binding.mqtt.homie/ @ccutrer
/bundles/org.openhab.binding.mqtt.ruuvigateway/ @ssalonen
/bundles/org.openhab.binding.mspa/ @weymann
/bundles/org.openhab.binding.mybmw/ @ntruchsess @mherwege @martingrassl
/bundles/org.openhab.binding.mycroft/ @dalgwen
/bundles/org.openhab.binding.mynice/ @clinique
@ -401,7 +401,7 @@
/bundles/org.openhab.binding.tesla/ @kgoderis
/bundles/org.openhab.binding.teslapowerwall/ @psmedley
/bundles/org.openhab.binding.teslascope/ @psmedley
/bundles/org.openhab.binding.tibber/ @kjoglum
/bundles/org.openhab.binding.tibber/ @kjoglum @weymann
/bundles/org.openhab.binding.tivo/ @mlobstein
/bundles/org.openhab.binding.touchwand/ @roieg
/bundles/org.openhab.binding.tplinkrouter/ @olivierkeke
@ -496,7 +496,7 @@
/itests/org.openhab.binding.astro.tests/ @gerrieg
/itests/org.openhab.binding.avmfritz.tests/ @cweitkamp
/itests/org.openhab.binding.feed.tests/ @openhab/add-ons-maintainers
/itests/org.openhab.binding.hue.tests/ @cweitkamp
/itests/org.openhab.binding.hue.tests/ @openhab/add-ons-maintainers
/itests/org.openhab.binding.max.tests/ @marcelrv
/itests/org.openhab.binding.mielecloud.tests/ @BjoernLange
/itests/org.openhab.binding.modbus.tests/ @ssalonen

View File

@ -24,7 +24,7 @@ received feedback on what to improve.
We're trying very hard to keep openHAB lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* openHAB.
that feature _on top of_ openHAB.
### Discuss your design in the discussion forum
@ -60,7 +60,7 @@ your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance.
and maintenance.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
@ -88,7 +88,7 @@ pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](https://developercertificate.org/)):
```
```text
Developer Certificate of Origin
Version 1.1
@ -129,20 +129,22 @@ By making a contribution to this project, I certify that:
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
```text
Signed-off-by: Joe Smith <joe.smith@email.com>
```
using your real name (sorry, no pseudonyms or anonymous contributions.) and an
e-mail address under which you can be reached (sorry, no github noreply e-mail
addresses (such as username@users.noreply.github.com) or other non-reachable
addresses (such as `username@users.noreply.github.com`) or other non-reachable
addresses are allowed).
On the command line you can use `git commit -s` to sign off the commit.
### How can I become a maintainer?
* Step 1: learn the component inside out
* Step 2: make yourself useful by contributing code, bugfixes, support etc.
* Step 3: volunteer on [the discussion group](https://github.com/openhab/openhab-addons/issues?labels=question&page=1&state=open)
- Step 1: learn the component inside out
- Step 2: make yourself useful by contributing code, bugfixes, support etc.
- Step 3: volunteer on [the discussion group](https://github.com/openhab/openhab-addons/issues?labels=question&page=1&state=open)
Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available.
You don't have to be a maintainer to make a difference on the project!
@ -153,22 +155,21 @@ We want to keep the openHAB community awesome, growing and collaborative. We
need your help to keep it that way. To help with this we have come up with some
general guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members: no
- Be nice: Be courteous, respectful and polite to fellow community members: no
regional, racial, gender, or other abuse will be tolerated. We like nice people
way better than mean ones!
* Encourage diversity and participation: Make everyone in our community
- Encourage diversity and participation: Make everyone in our community
feel welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
- Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break the
law.
* Stay on topic: Make sure that you are posting to the correct channel
- Stay on topic: Make sure that you are posting to the correct channel
and avoid off-topic discussions. Remember when you update an issue or
respond to an email you are potentially sending to a large number of
people. Please consider this before you update. Also remember that
nobody likes spam.

View File

@ -1,6 +1,6 @@
# openHAB Add-ons
<img align="right" width="220" src="./logo.png" />
<img align="right" width="220" src="logo.png" alt="openHAB logo" />
[![GitHub Actions Build Status](https://github.com/openhab/openhab-addons/actions/workflows/ci-build.yml/badge.svg?branch=main)](https://github.com/openhab/openhab-addons/actions/workflows/ci-build.yml)
[![Jenkins Build Status](https://ci.openhab.org/job/openHAB-Addons/badge/icon)](https://ci.openhab.org/job/openHAB-Addons/)
@ -11,18 +11,18 @@ This repository contains the official set of add-ons that are implemented on top
Add-ons that got accepted in here will be maintained (e.g. adapted to new core APIs)
by the [openHAB Add-on maintainers](https://github.com/orgs/openhab/teams/add-ons-maintainers).
To get started with binding development, follow our guidelines and tutorials over at https://www.openhab.org/docs/developer.
To get started with add-on development, follow our guidelines and tutorials over at <https://www.openhab.org/docs/developer>.
If you are interested in openHAB Core development, we invite you to come by on https://github.com/openhab/openhab-core.
If you are interested in openHAB Core development, we invite you to come by on <https://github.com/openhab/openhab-core>.
## Add-ons in other repositories
Some add-ons are not in this repository, but still part of the official [openHAB distribution](https://github.com/openhab/openhab-distro).
An incomplete list of other repositories follows below:
* https://github.com/openhab/org.openhab.binding.zwave
* https://github.com/openhab/org.openhab.binding.zigbee
* https://github.com/openhab/openhab-webui
- <https://github.com/openhab/org.openhab.binding.zwave>
- <https://github.com/openhab/org.openhab.binding.zigbee>
- <https://github.com/openhab/openhab-webui>
## Development / Repository Organization
@ -33,7 +33,7 @@ The official IDE (Integrated development environment) is Eclipse.
You find the following repository structure:
```
```text
.
+-- bom Maven buildsystem: Bill of materials
| +-- openhab-addons Lists all extensions for other repos to reference them
@ -76,7 +76,7 @@ mvn clean install -pl :org.openhab.binding.astro
If you have a binding that has dependencies that are dynamically as specified in the feature.xml you can create a `.kar` instead of a `.jar` file.
A `.kar` file will include the feature.xml and when added to openHAB will load and activate any dependencies specified in the feature.xml file.
To create a `.kar` file run maven with the goal `karaf:kar`:
To create a `.kar` file run Maven with the goal `karaf:kar`:
```shell
mvn clean install karaf:kar -pl :org.openhab.binding.astro
@ -112,7 +112,7 @@ When translations are added or updated and approved in Crowdin, a pull request i
Therefore translations should not be edited in the openHAB-addons repo, but only in Crowdin.
Otherwise translation are overridden by the automatic process.
To fill the English properties file run the following maven command on an add-on:
To fill the English properties file run the following Maven command on an add-on:
```shell
mvn i18n:generate-default-translations
@ -124,10 +124,9 @@ In some cases the command does not work, and requires the full plug-in name.
In that case use:
```shell
mvn org.openhab.core.tools:i18n-maven-plugin:3.4.0:generate-default-translations
mvn org.openhab.core.tools:i18n-maven-plugin:5.0.0:generate-default-translations
```
#### Code Quality
To check if your code is following the [code style](https://www.openhab.org/docs/developer/guidelines.html#b-code-formatting-rules-style) run:
@ -136,7 +135,7 @@ To check if your code is following the [code style](https://www.openhab.org/docs
mvn spotless:check
```
To reformat your code so it conforms to the code style you can run:
To reformat your code so it conforms to the code style you can run:
```shell
mvn spotless:apply
@ -145,7 +144,7 @@ mvn spotless:apply
### Integration Tests
When your add-on also has an integration test in the `itests` directory, you may need to update the runbundles in the `itest.bndrun` file when the Maven dependencies change.
Maven can resolve the integration test dependencies automatically by executing:
Maven can resolve the integration test dependencies automatically by executing:
```shell
mvn clean install -DwithResolver -DskipChecks
@ -157,6 +156,6 @@ The build generates a `.jar` file per bundle in the respective bundle `/target`
We have assembled some step-by-step guides for different IDEs on our developer documentation website:
https://www.openhab.org/docs/developer/#setup-the-development-environment
<https://www.openhab.org/docs/developer/#setup-the-development-environment>
Happy coding!

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bom</groupId>
<artifactId>org.openhab.addons.reactor.bom</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.addons.bom.openhab-addons</artifactId>
@ -651,11 +651,6 @@
<artifactId>org.openhab.binding.freeathome</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.freebox</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.freeboxos</artifactId>
@ -1281,6 +1276,11 @@
<artifactId>org.openhab.binding.mqtt.homie</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.mspa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.mycroft</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bom</groupId>
<artifactId>org.openhab.addons.reactor.bom</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.addons.bom.openhab-core-index</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons</groupId>
<artifactId>org.openhab.addons.reactor</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<groupId>org.openhab.addons.bom</groupId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bom</groupId>
<artifactId>org.openhab.addons.reactor.bom</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.addons.bom.runtime-index</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bom</groupId>
<artifactId>org.openhab.addons.reactor.bom</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.addons.bom.test-index</artifactId>

View File

@ -10,7 +10,7 @@ IF %ARGC% NEQ 3 (
exit /B 1
)
SET OpenhabVersion="5.0.0-SNAPSHOT"
SET OpenhabVersion="5.1.0-SNAPSHOT"
SET BindingIdInCamelCase=%~1
SET BindingIdInLowerCase=%BindingIdInCamelCase%

View File

@ -2,7 +2,7 @@
[ $# -lt 3 ] && { echo "Usage: $0 <BindingIdInCamelCase> <Author> <GitHub Username>"; exit 1; }
openHABVersion=5.0.0-SNAPSHOT
openHABVersion=5.1.0-SNAPSHOT
camelcaseId=$1
id=`echo $camelcaseId | tr '[:upper:]' '[:lower:]'`

View File

@ -1,6 +1,6 @@
# Groovy Scripting
This add-on provides support for [Groovy](https://groovy-lang.org/) 4.0.27 that can be used as a scripting language within automation rules and which eliminates the need to manually install Groovy.
This add-on provides support for [Groovy](https://groovy-lang.org/) 4.0.28 that can be used as a scripting language within automation rules.
## Creating Groovy Scripts

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.groovyscripting</artifactId>
@ -16,7 +16,7 @@
<properties>
<bnd.importpackage>com.ibm.icu.*;resolution:=optional,groovy.runtime.metaclass;resolution:=optional,groovyjarjarantlr4.stringtemplate;resolution:=optional,org.abego.treelayout.*;resolution:=optional,org.apache.ivy.*;resolution:=optional,org.fusesource.jansi.*;resolution:=optional,org.stringtemplate.v4.*;resolution:=optional</bnd.importpackage>
<groovy.version>4.0.27</groovy.version>
<groovy.version>4.0.28</groovy.version>
</properties>
<dependencies>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.jrubyscripting</artifactId>
@ -16,7 +16,7 @@
<properties>
<bnd.importpackage>com.sun.nio.*;resolution:=optional,com.sun.security.*;resolution:=optional,org.apache.tools.ant.*;resolution:=optional,org.bouncycastle.*;resolution:=optional,org.joda.*;resolution:=optional,sun.management.*;resolution:=optional,sun.nio.*;resolution:=optional,jakarta.annotation;resolution:=optional,jdk.crac.management;resolution:=optional</bnd.importpackage>
<jruby.version>10.0.0.1</jruby.version>
<jruby.version>10.0.2.0</jruby.version>
</properties>
<dependencies>

View File

@ -58,8 +58,8 @@ import org.slf4j.LoggerFactory;
public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
private final Logger logger = LoggerFactory.getLogger(JRubyConsoleCommandExtension.class);
private final String DEFAULT_CONSOLE_PATH = "openhab/console/";
private final String OPENHAB_SCRIPTING_GEM = "gem \"openhab-scripting\", \"~> 5.0\"";
private static final String DEFAULT_CONSOLE_PATH = "openhab/console/";
private static final String OPENHAB_SCRIPTING_GEM = "gem \"openhab-scripting\", \"~> 5.0\"";
private static final String INFO = "info";
private static final String CONSOLE = "console";
@ -173,7 +173,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
private void info(Console console) {
File gemfile = jRubyScriptEngineFactory.getConfiguration().getGemfile();
final String PRINT_VERSION_NUMBERS = """
final String printVersionNumbers = """
library_version = defined?(OpenHAB::DSL::VERSION) && OpenHAB::DSL::VERSION
puts "JRuby #{JRUBY_VERSION}"
@ -184,7 +184,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
puts "ENV['BUNDLE_GEMFILE']: #{ENV['BUNDLE_GEMFILE']}"
""");
executeWithFullJRuby(console, engine -> engine.eval(PRINT_VERSION_NUMBERS));
executeWithFullJRuby(console, engine -> engine.eval(printVersionNumbers));
console.println("Script path: " + scriptFileWatcher.getWatchPath());
console.println("");
console.println("JRuby Scripting Add-on Configuration:");
@ -270,7 +270,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
for (Map.Entry<String, String> consoleScript : consoles.entrySet()) {
String name = consoleScript.getKey();
String description = consoleScript.getValue();
if (defaultConsole.equals(DEFAULT_CONSOLE_PATH + name) || defaultConsole.equals(name)) {
if ((DEFAULT_CONSOLE_PATH + name).equals(defaultConsole) || name.equals(defaultConsole)) {
description = description + " (default)";
defaultConsoleInRegistry = true;
}
@ -312,7 +312,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
});
}
synchronized private void bundler(Console console, String[] args) {
private synchronized void bundler(Console console, String[] args) {
final File gemfile = jRubyScriptEngineFactory.getConfiguration().getGemfile();
boolean bundleInit = args.length >= 1 && "init".equals(args[0]);
@ -331,7 +331,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
return;
}
final String BUNDLER = """
final String bundler = """
require "jruby"
JRuby.runtime.instance_config.update_native_env_enabled = false
@ -363,7 +363,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
try {
Object result = executeWithPlainJRuby(console, engine -> {
engine.put(ScriptEngine.ARGV, args);
return engine.eval(BUNDLER);
return engine.eval(bundler);
});
logger.debug("Bundler result: {}", result);
// A null result indicates a successful creation of Gemfile.
@ -409,7 +409,7 @@ public class JRubyConsoleCommandExtension extends AbstractConsoleCommandExtensio
Files.write(gemfilePath, outputGemfile);
}
synchronized private void gem(Console console, String[] args) {
private synchronized void gem(Console console, String[] args) {
final String GEM = """
require "rubygems/gem_runner"
Gem::GemRunner.new.run ARGV

View File

@ -54,7 +54,7 @@ import org.osgi.service.component.annotations.ReferencePolicy;
property = Constants.SERVICE_PID + "=org.openhab.automation.jrubyscripting")
@ConfigurableService(category = "automation", label = "JRuby Scripting", description_uri = JRubyScriptEngineFactory.CONFIG_DESCRIPTION_URI)
public class JRubyScriptEngineFactory extends AbstractScriptEngineFactory {
public final static String CONFIG_DESCRIPTION_URI = "automation:jrubyscripting";
public static final String CONFIG_DESCRIPTION_URI = "automation:jrubyscripting";
private final JRubyScriptEngineConfiguration configuration = new JRubyScriptEngineConfiguration();

View File

@ -10,28 +10,20 @@ to common openHAB functionality within rules including items, things, actions, l
## Configuration
This add-on includes by default the [openhab-js](https://github.com/openhab/openhab-js/) NPM library and exports its namespaces onto the global namespace.
This add-on includes a version of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library.
This allows the use of `items`, `actions`, `cache` and other objects without the need to explicitly import them using `require()`.
This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options.
Depending on the add-on configuration, it automatically exports all namespaces (see [Standard Library](#standard-library)) onto the global namespace.
This allows the use of `items`, `actions`, `cache` and other APIs from the UI without the need to explicitly import them using `require()`.
This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options:
By default, the injection of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library is cached (using a special mechanism instead of `require()`) to improve performance and reduce memory usage.
- Only inject the openhab-js namespaces globally for UI-based rules and scripts (recommended and default).
- Inject the openhab-js namespaces globally for all scripts, including file-based rules and transformations.
- Disable injection of the openhab-js namespaces everywhere, which means you need to import the library using `require()` in every script that uses it.
When configuring the add-on, you should ask yourself these questions:
If enabled, the injection of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library is cached (using a special mechanism instead of `require()`) to improve script loading performance.
This can be disabled, which will allow you to use a different version of the library than the one included in the add-on.
1. Do I want to have the openhab-js namespaces automatically globally available (`injectionEnabled`)?
- Yes: "Use Built-In Variables" (default)
- No: "Do Not Use Built-In Variables", which will allow you to decide what to import and really speed up script loading, but you need to manually import the library, which actually will slow down script loading again.
2. Do I want to have a different version injected other than the included one (`injectionCachingEnabled`)?
- Yes: "Do Not Cache Library Injection" and install your version to the `$OPENHAB_CONF/automation/js/node_modules` folder, which will slow down script loading, because the injection is not cached.
- No: "Cache Library Injection" (default), which will speed up the initial loading of a script because the library's injection is cached.
Note that in case you disable caching or your code uses `require()` to import the library and there is no installation of the library found in the node_modules folder, the add-on will fallback to its included version.
In general, the first run of a script will take longer than the subsequent runs.
This is because on the first run both the globals (like `console`) and (if enabled) the library are injected into the script's context.
<!-- Paste the copied docs from openhab-js under this comment. Do NOT forget the table of contents. -->
<!-- Paste the copied docs from openhab-js under this comment. -->
### UI Based Rules
@ -79,16 +71,57 @@ console.log("Thing status",thingStatusInfo.getStatus());
See [openhab-js](https://openhab.github.io/openhab-js) for a complete list of functionality.
### UI Event Object
### Event Object
**NOTE**: Note that `event` object is different in UI based rules and file based rules! This section is only valid for UI based rules. If you use file based rules, refer to [file based rules event object documentation](#event-object).
Note that `event` object is only available when:
1. the UI based rule was triggered by an event and is not manually run! Trying to access `event` on a manual run does not work (and will lead to an error), use `this.event` instead (will be `undefined` in case of manual run).
2. the rule is inline script or called by an inline script which passes it the `event object`. "Item Actions", "Scene, Rules, & Scripts", and "Audio and Voice" actions are not passed the `event` object.
When a rule is triggered, the script is provided the event instance that triggered it.
The specific data depends on the event type.
The `event` object provides some information about that trigger.
When you use "Item event" as trigger (i.e. "[item] received a command", "[item] was updated", "[item] changed"), there is additional context available for the action in a variable called `event`.
This table gives an overview over the `event` object:
This table gives an overview over the `event` object for most common trigger types:
| Property Name | Trigger Types | Description | Rules DSL Equivalent |
|-------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------|------------------------|
| `oldState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | Previous state of Item or Group that triggered event | `previousState` |
| `newState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | New state of Item or Group that triggered event | N/A |
| `receivedState` | `ItemStateUpdateTrigger`, `GroupStateUpdateTrigger` | State of Item that triggered event | `triggeringItem.state` |
| `receivedCommand` | `ItemCommandTrigger`, `GroupCommandTrigger` | Command that triggered event | `receivedCommand` |
| `itemName` | `Item****Trigger`, `Group****Trigger` | Name of Item that triggered event | `triggeringItem.name` |
| `groupName` | `Group****Trigger` | Name of the group whose member triggered event | N/A |
| `receivedEvent` | `ChannelEventTrigger` | Channel event that triggered event | N/A |
| `channelUID` | `ChannelEventTrigger` | UID of channel that triggered event | N/A |
| `oldStatus` | `ThingStatusChangeTrigger` | Previous state of Thing that triggered event | N/A |
| `newStatus` | `ThingStatusChangeTrigger` | New state of Thing that triggered event | N/A |
| `status` | `ThingStatusUpdateTrigger` | State of Thing that triggered event | N/A |
| `thingUID` | `Thing****Trigger` | UID of Thing that triggered event | N/A |
| `cronExpression` | `GenericCronTrigger` | Cron expression of the trigger | N/A |
| `time` | `TimeOfDayTrigger` | Time of day value of the trigger | N/A |
| `timeOnly` | `DateTimeTrigger` | Whether the trigger only considers the time part of the DateTime Item | N/A |
| `offset` | `DateTimeTrigger` | Offset in seconds added to the time of the DateTime Item | N/A |
| `eventType` | all except `PWMTrigger`, `PIDTrigger` | Type of event that triggered event (change, command, triggered, update, time) | N/A |
| `triggerType` | all except `PWMTrigger`, `PIDTrigger` | Type of trigger that triggered event | N/A |
| `eventName` | all | simple Java class name of the triggering event, e.g. `ExecutionEvent` | N/A |
| `eventClass` | all | full Java class name of the triggering event, e.g. `org.openhab.core.automation.events.ExecutionEvent` | N/A |
| `module` | all | (user-defined or auto-generated) name of trigger | N/A |
| `raw` | all | Original contents of the event including data passed from a calling rule | N/A |
All properties are typeof `string` except for properties contained by `raw` which are unmodified from the original types.
Please note that when using `GenericEventTrigger`, the available properties depend on the chosen event types.
It is not possible for the openhab-js library to provide type conversions for all properties of all openHAB events, as those are too many.
In case the event object does not provide type-conversed properties for your chosen event type, use the `payload` property to gain access to the event's (Java data type) payload.
**NOTE:**
`Group****Trigger`s use the equivalent `Item****Trigger` as trigger for each member.
See [openhab-js : EventObject](https://openhab.github.io/openhab-js/rules.html#.EventObject) for full API documentation.
When disabling the option _Convert Event from Java to JavaScript type in UI-based scripts_, you will receive a raw Java event object instead of the `event` object described above.
See the expandable section below for more details.
<details>
<summary>Raw UI Event Object</summary>
This table gives an overview over the raw Java `event` object for UI-based scripts for most common trigger types:
| Property Name | Type | Trigger Types | Description | Rules DSL Equivalent |
|----------------|----------------------------------------------------------------------------------------------------------------------|----------------------------------------|---------------------------------------------------------------------------------------------------------------|------------------------|
@ -97,8 +130,10 @@ This table gives an overview over the `event` object for most common trigger typ
| `itemCommand` | sub-class of [org.openhab.core.types.Command](https://www.openhab.org/javadoc/latest/org/openhab/core/types/command) | `[item] received a command` | Command that triggered event | `receivedCommand` |
| `itemName` | string | all | Name of Item that triggered event | `triggeringItem.name` |
| `type` | string | all | Type of event that triggered event (`"ItemStateEvent"`, `"ItemStateChangedEvent"`, `"ItemCommandEvent"`, ...) | N/A |
| `event` | string | channel based triggeres | Event data published by the triggering channel. | `receivedEvent` |
| `payload` | JSON formatted string | all | Any additional information provided by the trigger not already exposed. "{}" there is none. | N/A |
Note that in UI based rules `event.itemState`, `event.oldItemState`, and `event.itemCommand` are Java types (not JavaScript), and care must be taken when comparing these with JavaScript types:
Note that in UI based rules `event`, and therefore everything carried by `event` are Java types (not JavaScript). Care must be taken when comparing these with JavaScript types:
```javascript
var { ON } = require("@runtime")
@ -116,30 +151,12 @@ console.log(event.itemState == "test") // WRONG. Will always log "false"
console.log(event.itemState.toString() == "test") // OK
```
</details>
## Scripting Basics
The openHAB JavaScript Scripting runtime attempts to provide a familiar environment to JavaScript developers.
### `let` and `const`
Due to the way how openHAB runs UI based scripts, `let`, `const` and `class` are not supported at top-level.
Use `var` instead or wrap your script inside a self-invoking function:
```javascript
// Wrap script inside a self-invoking function:
(function (data) {
const C = 'Hello world';
console.log(C);
})(this.event);
// Defining a class using var:
var Tree = class {
constructor (height) {
this.height = height;
}
}
```
### `require`
Scripts may include standard NPM based libraries by using CommonJS `require`.
@ -239,7 +256,6 @@ myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello mutation!"
If you need to pass some variables to the timer but avoid that they can get mutated, pass those variables as parameters to `setTimeout`/`setInterval` or `createTimer`:
```javascript
var myVar = 'Hello world!';
@ -290,8 +306,8 @@ Use JavaScript Scripting as script transformation by:
})(input);
```
2. Using `JS(<scriptname>.js):%s` as Item state transformation.
3. Passing parameters is also possible by using a URL like syntax: `JS(<scriptname>.js?arg=value)`.
1. Using `JS(<scriptname>.js):%s` as Item state transformation.
1. Passing parameters is also possible by using a URL like syntax: `JS(<scriptname>.js?arg=value)`.
Parameters are injected into the script and can be referenced like variables.
Simple transformations can aso be given as an inline script: `JS(|...)`, e.g. `JS(|"String has " + input.length + "characters")`.
@ -318,10 +334,12 @@ See [openhab-js : items](https://openhab.github.io/openhab-js/items.html) for fu
- .getItem(name, nullIfMissing) ⇒ `Item`
- .getItems() ⇒ `Array[Item]`
- .getItemsByTag(...tagNames) ⇒ `Array[Item]`
- .addItem([itemConfig](#itemconfig))
- .removeItem(itemOrItemName) ⇒ `boolean`
- .replaceItem([itemConfig](#itemconfig))
- .addItem([itemConfig](#itemconfig), persist)`Item`
- .removeItem(itemOrItemName) ⇒ `Item|null`
- .replaceItem([itemConfig](#itemconfig))`Item|null`
- .safeItemName(s) ⇒ `string`
- .metadata ⇒ [`items.metadata` namespace](https://openhab.github.io/openhab-js/items.metadata.html): Manage metadata directly without the need of going "through" the Item
- .itemChannelLink ⇒ [`items.itemChannelLink` namespace](https://openhab.github.io/openhab-js/items.itemChannelLink.html): Manage Item -> channel links
```javascript
var item = items.KitchenLight;
@ -337,11 +355,13 @@ Calling `getItem(...)` or `...` returns an `Item` object with the following prop
- .persistence ⇒ [`ItemPersistence`](#itempersistence)
- .semantics ⇒ [`ItemSemantics`](https://openhab.github.io/openhab-js/items.ItemSemantics.html)
- .type ⇒ `string`
- .groupType ⇒ `string|null`
- .name ⇒ `string`
- .label ⇒ `string`
- .state ⇒ `string`
- .numericState ⇒ `number|null`: State as number, if state can be represented as number, or `null` if that's not the case
- .quantityState ⇒ [`Quantity|null`](#quantity): Item state as Quantity or `null` if state is not Quantity-compatible or without unit
- .boolState ⇒ `boolean|null`: Item state as boolean or `null` if not boolean-compatible or is NULL or UNDEF, see below for mapping of state to boolean
- .rawState ⇒ `HostState`
- .previousState ⇒ `string|null`: Previous state as string, or `null` if not available
- .previousNumericState ⇒ `number|null`: Previous state as number, if state can be represented as number, or `null` if that's not the case or not available
@ -383,6 +403,25 @@ item.postUpdate("OFF");
console.log("KitchenLight state", item.state);
```
The `boolState` property is mapped according to the following table:
| Item Type | `.boolState` |
|--------------------|----------------------------------------------------|
| Call | `null` |
| Color | brightness > 0 |
| Contact | state === `OPEN` |
| DateTime | `null` |
| Dimmer | state > 0 |
| Group | `null` if no group type, else use the group state |
| Image | `null` |
| Location | `null` |
| Number | state !== 0 |
| Number:<Dimension> | state !== 0 |
| Player | state === `PLAY` |
| Rollershutter | state > 0 |
| String | `null` |
| Switch | state === `ON` |
See [openhab-js : Item](https://openhab.github.io/openhab-js/items.Item.html) for full API documentation.
#### `itemConfig`
@ -390,24 +429,38 @@ See [openhab-js : Item](https://openhab.github.io/openhab-js/items.Item.html) fo
Calling `addItem(itemConfig)` or `replaceItem(itemConfig)` requires the `itemConfig` object with the following properties:
- itemConfig : `object`
- .type ⇒ `string`
- .name ⇒ `string`
- .label ⇒ `string`
- .category (icon) ⇒ `string`
- .groups ⇒ `Array[string]`
- .tags ⇒ `Array[string]`
- .type ⇒ `string`: required, e.g. `Switch` or `Group`
- .name ⇒ `string`: required
- .label ⇒ `string`: optional
- .category (icon) ⇒ `string`: optional
- .groups ⇒ `Array[string]`: optional names of groups to be a member of
- .tags ⇒ `Array[string]`: optional
- .group ⇒ `object`: optional additional config if Item is group Item
- .type ⇒ `string`: optional type of the group, e.g. `Switch`
- .function ⇒ `string`: optional aggregation function, e.g. `AND`
- .parameters ⇒ `Array[string]`: parameters possibly required by aggregation function, e.g. `ON` and `OFF`
- .channels ⇒ `string | Object { channeluid: { config } }`
- .metadata ⇒ `Object { namespace: value } | Object { namespace: { value: value , config: { config } } }`
- .giBaseType ⇒ `string`
- .groupFunction ⇒ `string`
- .metadata ⇒ `Object { namespace: 'value' } | Object { namespace: { value: '' , configuration: { ... } } }`
There are a few short forms for common metadata available:
- itemConfig : `object`
- .format ⇒ `string`: short form for `stateDescription` metadata's pattern configuration
- .unit ⇒ `string`: short form for the `unit` metadata
- .autoupdate ⇒ `boolean`: short form for the `autoupdate` metadata
Note: `.type` and `.name` are required.
Basic UI and the mobile apps need `metadata.stateDescription.config.pattern` to render the state of an Item.
Basic UI and the mobile apps need `metadata.stateDescription.configuration.pattern` to render the state of an Item.
Example:
```javascript
// more advanced example
items.replaceItem({
type: 'Switch',
name: 'MySwitch',
label: 'My Switch'
});
items.replaceItem({
type: 'String',
name: 'Hallway_Light',
@ -431,22 +484,35 @@ items.replaceItem({
}
}
});
// minimal example
items.replaceItem({
type: 'Switch',
name: 'MySwitch',
metadata: {
stateDescription: {
config: {
pattern: '%s'
}
}
type: 'Group',
name: 'gLights',
label: 'Lights',
group: {
type: 'Switch',
function: 'AND',
parameters: ['ON', 'OFF']
}
});
```
See [openhab-js : ItemConfig](https://openhab.github.io/openhab-js/global.html#ItemConfig) for full API documentation.
#### Providing Items (& metadata & channel links) from Scripts
The `addItem` method can be used to provide Items from scripts in a configuration-as-code manner.
It also allows providing metadata and channel configurations for the Item, basically creating the Item as if it was defined in a `.items` file.
The benefit of using `addItem` is that you can use loops, conditions or generator functions to create lots of Items without the need to write them all out in a file or manually in the UI.
When called from file-based scripts, the created Item will share the lifecycle with the script, meaning it will be removed when the script is unloaded.
You can use the `persist` parameter to optionally persist the Item from file-based scripts.
When called from UI-based scripts, the Item will be stored permanently and will not be removed when the script is unloaded.
Keep in mind that attempting to add an Item with the same name as an existing Item will result in an error.
See [openhab-js : Item](https://openhab.github.io/openhab-js/items.html#.addItem) for full API documentation.
#### `ItemPersistence`
Calling `Item.persistence` returns an `ItemPersistence` object with the following functions:
@ -729,7 +795,6 @@ actions.ScriptExecution.createTimer(string identifier, time.ZonedDateTime zdt, f
- `hasTerminated()`: Whether the scheduled execution has already terminated. ⇒ `boolean`
- `reschedule(time.ZonedDateTime)`: Reschedules a timer to a new starting time. This can also be called after a timer has terminated, which will result in another execution of the same code. ⇒ `boolean`: true, if rescheduling was successful
```javascript
var now = time.ZonedDateTime.now();
@ -875,14 +940,14 @@ See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for fu
The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value.
**Example** *(Get a previously set value with a default value (times = 0))*
**Example** _(Get a previously set value with a default value (times = 0))_
```js
var counter = cache.shared.get('counter', () => 0);
console.log('Counter: ' + counter);
```
**Example** *(Get a previously set value, modify and store it)*
**Example** _(Get a previously set value, modify and store it)_
```js
var counter = cache.private.get('counter');
@ -923,7 +988,7 @@ Therefore, if you attempt to use the `DateTimeFormatter` and receive an error sa
[JS-Joda Locales](https://github.com/js-joda/js-joda/tree/master/packages/locale#use-prebuilt-locale-packages) includes a list of all the supported locales.
Each locale consists of a two letter language indicator followed by a "-" and a two letter dialect indicator: e.g. "EN-US".
Installing a locale can be done through the command `npm install @js-joda/locale_de-de` from the *$OPENHAB_CONF/automation/js* folder.
Installing a locale can be done through the command `npm install @js-joda/locale_de-de` from the _$OPENHAB_CONF/automation/js_ folder.
To import and use a local into your rule you need to require it and create a `DateTimeFormatter` that uses it:
@ -1204,6 +1269,9 @@ Local variable state is not persisted among reloads, see using the [cache](#cach
File based rules can be created in 2 different ways: using [JSRule](#jsrule) or the [Rule Builder](#rule-builder).
When a rule is triggered, the script is provided information about the event that triggered the rule in the `event` object.
Please refer to [Event Object](#event-object) for documentation.
See [openhab-js : rules](https://openhab.github.io/openhab-js/rules.html) for full API documentation.
### JSRule
@ -1298,14 +1366,14 @@ Rule are completed by calling `.build(name, description, tags, id)` , all parame
A simple example of this would look like:
```javascript
rules.when().item("F1_Light").changed().then().send("changed").toItem("F2_Light").build("My Rule", "My First Rule");
rules.when().item("F1_Light").changed().then().send("changed").toItem("F2_Light").build("My Rule", "My First Rule", ['MyTag1', 'Tag2'], 'MyRuleID');
```
Operations and conditions can also optionally take functions:
```javascript
rules.when().item("F1_light").changed().then(event => {
console.log(event);
console.log(event);
}).build("Test Rule", "My Test Rule");
```
@ -1424,54 +1492,6 @@ rules.when(true).item('HallLight').receivedCommand().then().sendIt().toItem('Kit
rules.when(true).item('HallLight').receivedUpdate().then().copyState().fromItem('BedroomLight1').toItem('BedroomLight2').build();
```
### Event Object
**NOTE**: The `event` object is different in UI Based Rules and File Based Rules!
This section is only valid for File Based Rules.
If you use UI Based Rules, refer to [UI based rules event object documentation](#ui-event-object).
When a rule is triggered, the script is provided the event instance that triggered it.
The specific data depends on the event type.
The `event` object provides some information about that trigger.
This table gives an overview over the `event` object:
| Property Name | Trigger Types | Description | Rules DSL Equivalent |
|-------------------|-----------------------------------------------------|-------------------------------------------------------------------------------|------------------------|
| `oldState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | Previous state of Item or Group that triggered event | `previousState` |
| `newState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | New state of Item or Group that triggered event | N/A |
| `receivedState` | `ItemStateUpdateTrigger`, `GroupStateUpdateTrigger` | State of Item that triggered event | `triggeringItem.state` |
| `receivedCommand` | `ItemCommandTrigger`, `GroupCommandTrigger` | Command that triggered event | `receivedCommand` |
| `itemName` | `Item****Trigger`, `Group****Trigger` | Name of Item that triggered event | `triggeringItem.name` |
| `groupName` | `Group****Trigger` | Name of the group whose member triggered event | N/A |
| `receivedEvent` | `ChannelEventTrigger` | Channel event that triggered event | N/A |
| `channelUID` | `ChannelEventTrigger` | UID of channel that triggered event | N/A |
| `oldStatus` | `ThingStatusChangeTrigger` | Previous state of Thing that triggered event | N/A |
| `newStatus` | `ThingStatusChangeTrigger` | New state of Thing that triggered event | N/A |
| `status` | `ThingStatusUpdateTrigger` | State of Thing that triggered event | N/A |
| `thingUID` | `Thing****Trigger` | UID of Thing that triggered event | N/A |
| `cronExpression` | `GenericCronTrigger` | Cron expression of the trigger | N/A |
| `time` | `TimeOfDayTrigger` | Time of day value of the trigger | N/A |
| `timeOnly` | `DateTimeTrigger` | Whether the trigger only considers the time part of the DateTime Item | N/A |
| `offset` | `DateTimeTrigger` | Offset in seconds added to the time of the DateTime Item | N/A |
| `eventType` | all except `PWMTrigger`, `PIDTrigger` | Type of event that triggered event (change, command, triggered, update, time) | N/A |
| `triggerType` | all except `PWMTrigger`, `PIDTrigger` | Type of trigger that triggered event | N/A |
| `eventClass` | all | Java class name of the triggering event | N/A |
| `module` | all | (user-defined or auto-generated) name of trigger | N/A |
| `raw` | all | Original contents of the event including data passed from a calling rule | N/A |
All properties are typeof `string` except for properties contained by `raw` which are unmodified from the original types.
Please note that when using `GenericEventTrigger`, the available properties depend on the chosen event types.
It is not possible for the openhab-js library to provide type conversions for all properties of all openHAB events, as those are too many.
In case the event object does not provide type-conversed properties for your chosen event type, use the `payload` property to gain access to the event's (Java data type) payload.
**NOTE:**
`Group****Trigger`s use the equivalent `Item****Trigger` as trigger for each member.
Time triggers do not provide any event instance, therefore no property is populated.
See [openhab-js : EventObject](https://openhab.github.io/openhab-js/rules.html#.EventObject) for full API documentation.
## Advanced Scripting
### Libraries
@ -1499,8 +1519,8 @@ When it is run, `npm` will remove everything from `node_modules` that has not be
Follow these steps to create your own library (it's called a CommonJS module):
1. Create a separate folder for your library outside of `automation/js`, you may also initialize a Git repository.
2. Run `npm init` from your newly created folder; at least provide responses for the `name`, `version` and `main` (e.g. `index.js`) fields.
3. Create the main file of your library (`index.js`) and add some exports:
1. Run `npm init` from your newly created folder; at least provide responses for the `name`, `version` and `main` (e.g. `index.js`) fields.
1. Create the main file of your library (`index.js`) and add some exports:
```javascript
var someProperty = 'Hello world!';
@ -1514,9 +1534,12 @@ Follow these steps to create your own library (it's called a CommonJS module):
};
```
4. Tar it up by running `npm pack` from your library's folder.
5. Install it by running `npm install <path-to-library-folder>/<name>-<version>.tgz` from the `automation/js` folder.
6. After you've installed it with `npm`, you can continue development of the library inside `node_modules`.
1. Tar it up by running `npm pack` from your library's folder.
1. Install it by running `npm install <path-to-library-folder>/<name>-<version>.tgz` from the `automation/js` folder.
1. After you've installed it with `npm`, you can continue development of the library inside `node_modules`.
1. As you might have already noticed, the JavaScript Scripting add-on is reloading a script as soon as one of its dependencies changes.
When developing a library inside `node_modules`, this can cause regular reloads.
To avoid this situation, you can disable dependency tracking in the JavaScript Scripting add-on settings (you need to tick "show advanced" for the setting to come up).
It is also possible to upload your library to [npm](https://npmjs.com) to share it with other users.

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.jsscripting</artifactId>
@ -17,7 +17,7 @@
<properties>
<!-- Remember to check if the fix https://github.com/openhab/openhab-core/pull/4437 still works when upgrading GraalJS -->
<graaljs.version>24.2.1</graaljs.version>
<ohjs.version>openhab@5.11.1</ohjs.version>
<ohjs.version>openhab@5.14.0</ohjs.version>
</properties>
<dependencies>
@ -65,7 +65,7 @@
</goals>
<configuration>
<!--suppress UnresolvedMavenProperty -->
<arguments>install ${ohjs.version} webpack@^5.94.0 webpack-cli@^5.1.4 --prefix .</arguments>
<arguments>install ${ohjs.version} webpack@^5.101.3 webpack-cli@^5.1.4 --prefix .</arguments>
<!-- webpack & webpack-cli versions should match to the ones from openhab-js -->
</configuration>
</execution>

View File

@ -12,6 +12,7 @@
*/
package org.openhab.automation.jsscripting.internal;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.CONTEXT_KEY_ENGINE_IDENTIFIER;
import static org.openhab.core.automation.module.script.ScriptTransformationService.OPENHAB_TRANSFORMATION_SCRIPT;
import java.util.Arrays;
@ -72,14 +73,22 @@ class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable & AutoClosea
// OPS4J Pax Logging holds a reference to the exception, which causes the OpenhabGraalJSScriptEngine to not be
// removed from heap by garbage collection and causing a memory leak.
// Therefore, don't pass the exceptions itself to the logger, but only their message!
if (cause instanceof IllegalArgumentException) {
logger.error("Failed to execute script: {}", stringifyThrowable(cause));
} else if (cause instanceof PolyglotException) {
logger.error("Failed to execute script: {}", stringifyThrowable(cause));
if (cause instanceof IllegalArgumentException || cause instanceof PolyglotException) {
String strT = stringifyThrowable(cause);
logger.error("Failed to execute script: {}", strT);
}
return e;
}
@Override
public void close() {
try {
super.close();
} catch (Exception e) {
logger.warn("Ignorable exception during close: {}", stringifyThrowable(e));
}
}
private String stringifyThrowable(Throwable throwable) {
String message = throwable.getMessage();
StackTraceElement[] stackTraceElements = throwable.getStackTrace();
@ -98,17 +107,16 @@ class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable & AutoClosea
ScriptContext ctx = delegate.getContext();
Object fileName = ctx.getAttribute("javax.script.filename");
Object ruleUID = ctx.getAttribute("ruleUID");
Object ohEngineIdentifier = ctx.getAttribute("oh.engine-identifier");
Object ohEngineIdentifier = ctx.getAttribute(CONTEXT_KEY_ENGINE_IDENTIFIER);
String identifier = "stack";
if (fileName != null) {
identifier = fileName.toString().replaceAll("^.*[/\\\\]", "");
} else if (ruleUID != null) {
identifier = ruleUID.toString();
} else if (ohEngineIdentifier != null) {
if (ohEngineIdentifier.toString().startsWith(OPENHAB_TRANSFORMATION_SCRIPT)) {
identifier = ohEngineIdentifier.toString().replaceAll(OPENHAB_TRANSFORMATION_SCRIPT, "transformation.");
}
} else if (ohEngineIdentifier != null
&& ohEngineIdentifier.toString().startsWith(OPENHAB_TRANSFORMATION_SCRIPT)) {
identifier = ohEngineIdentifier.toString().replaceAll(OPENHAB_TRANSFORMATION_SCRIPT, "transformation.");
}
logger = LoggerFactory.getLogger("org.openhab.automation.script.javascript." + identifier);

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2010-2025 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.automation.jsscripting.internal;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ConfigParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes JavaScript Configuration Parameters.
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public class GraalJSScriptEngineConfiguration {
Logger logger = LoggerFactory.getLogger(GraalJSScriptEngineConfiguration.class);
private static final String CFG_INJECTION_ENABLED = "injectionEnabledV2";
private static final String CFG_INJECTION_CACHING_ENABLED = "injectionCachingEnabled";
private static final String CFG_WRAPPER_ENABLED = "wrapperEnabled";
private static final String CFG_EVENT_CONVERSION_ENABLED = "eventConversionEnabled";
private static final String CFG_DEPENDENCY_TRACKING_ENABLED = "dependencyTrackingEnabled";
private static final int INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY = 1;
private static final int INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_AND_TRANSFORMATIONS = 2;
private static final int INJECTION_ENABLED_FOR_ALL_SCRIPTS = 3;
private int injectionEnabled = INJECTION_ENABLED_FOR_ALL_SCRIPTS;
private boolean injectionCachingEnabled = true;
private boolean wrapperEnabled = true;
private boolean eventConversionEnabled = true;
private boolean dependencyTrackingEnabled = true;
/**
* Create a new configuration instance from the given parameters.
*
* @param config configuration parameters to apply to JavaScript
*/
public GraalJSScriptEngineConfiguration(Map<String, ?> config) {
update(config);
}
/**
* To be called when the configuration is modified.
*
* @param config configuration parameters to apply to JavaScript
*/
void modified(Map<String, ?> config) {
boolean oldInjectionEnabledForUiBasedScript = isInjectionEnabledForUiBasedScript();
boolean oldDependencyTrackingEnabled = dependencyTrackingEnabled;
boolean oldWrapperEnabled = wrapperEnabled;
boolean oldEventConversionEnabled = eventConversionEnabled;
this.update(config);
if (oldInjectionEnabledForUiBasedScript != isInjectionEnabledForUiBasedScript()
&& !isInjectionEnabledForUiBasedScript() && isEventConversionEnabled()) {
logger.warn(
"Injection disabled for UI-based scripts, but event conversion is enabled. Event conversion will not work.");
}
if (oldDependencyTrackingEnabled != dependencyTrackingEnabled) {
logger.info(
"{} dependency tracking for JavaScript Scripting. Please resave your scripts to apply this change.",
dependencyTrackingEnabled ? "Enabled" : "Disabled");
}
if (oldWrapperEnabled != wrapperEnabled) {
logger.info(
"{} wrapper for JavaScript Scripting. Please resave your UI-based scripts to apply this change.",
wrapperEnabled ? "Enabled" : "Disabled");
}
if (oldEventConversionEnabled != eventConversionEnabled) {
if (eventConversionEnabled && (!isInjectionEnabledForUiBasedScript() || !wrapperEnabled)) {
logger.warn(
"Enabled event conversion for UI-based scripts, but auto-injection or wrapper is disabled. Event conversion will not work.");
}
if (!eventConversionEnabled) {
logger.info(
"Disabled event conversion for JavaScript Scripting. Please resave your scripts to apply this change.");
}
}
}
/**
* Update configuration
*
* @param config configuration parameters to apply to JavaScript
*/
private void update(Map<String, ?> config) {
logger.trace("JavaScript Script Engine Configuration: {}", config);
injectionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_ENABLED), Integer.class,
INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY);
injectionCachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_CACHING_ENABLED), Boolean.class,
true);
wrapperEnabled = ConfigParser.valueAsOrElse(config.get(CFG_WRAPPER_ENABLED), Boolean.class, true);
eventConversionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_EVENT_CONVERSION_ENABLED), Boolean.class,
true);
dependencyTrackingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_DEPENDENCY_TRACKING_ENABLED),
Boolean.class, true);
}
public boolean isInjectionEnabledForUiBasedScript() {
return injectionEnabled >= INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY;
}
public boolean isInjectionEnabledForTransformations() {
return injectionEnabled >= INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_AND_TRANSFORMATIONS;
}
public boolean isInjectionEnabledForAllScripts() {
return injectionEnabled == INJECTION_ENABLED_FOR_ALL_SCRIPTS;
}
public boolean isInjectionCachingEnabled() {
return injectionCachingEnabled;
}
public boolean isWrapperEnabled() {
return wrapperEnabled;
}
public boolean isEventConversionEnabled() {
return eventConversionEnabled;
}
public boolean isDependencyTrackingEnabled() {
return dependencyTrackingEnabled;
}
}

View File

@ -26,13 +26,14 @@ import org.openhab.automation.jsscripting.internal.fs.watch.JSDependencyTracker;
import org.openhab.core.OpenHAB;
import org.openhab.core.automation.module.script.ScriptDependencyTracker;
import org.openhab.core.automation.module.script.ScriptEngineFactory;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.config.core.ConfigurableService;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory;
@ -46,29 +47,26 @@ import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory;
+ "=org.openhab.jsscripting")
@ConfigurableService(category = "automation", label = "JS Scripting", description_uri = "automation:jsscripting")
@NonNullByDefault
public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
public class GraalJSScriptEngineFactory implements ScriptEngineFactory {
public static final Path JS_DEFAULT_PATH = Paths.get(OpenHAB.getConfigFolder(), "automation", "js");
public static final String NODE_DIR = "node_modules";
public static final Path JS_LIB_PATH = JS_DEFAULT_PATH.resolve(NODE_DIR);
public static final String SCRIPT_TYPE = "application/javascript";
private static final String CFG_INJECTION_ENABLED = "injectionEnabled";
private static final String CFG_INJECTION_CACHING_ENABLED = "injectionCachingEnabled";
private static final GraalJSEngineFactory FACTORY = new GraalJSEngineFactory();
private static final GraalJSEngineFactory factory = new GraalJSEngineFactory();
private static final List<String> scriptTypes = createScriptTypes();
private static final List<String> SCRIPT_TYPES = createScriptTypes();
private static List<String> createScriptTypes() {
// Add those for backward compatibility (existing scripts may rely on those MIME types)
List<String> backwardCompat = List.of("application/javascript;version=ECMAScript-2021", "graaljs");
return Stream.of(List.of(SCRIPT_TYPE), factory.getMimeTypes(), factory.getExtensions(), backwardCompat)
return Stream.of(List.of(SCRIPT_TYPE), FACTORY.getMimeTypes(), FACTORY.getExtensions(), backwardCompat)
.flatMap(List::stream).distinct().toList();
}
private boolean injectionEnabled = true;
private boolean injectionCachingEnabled = true;
private final Logger logger = LoggerFactory.getLogger(GraalJSScriptEngineFactory.class);
private final GraalJSScriptEngineConfiguration configuration;
private final JSScriptServiceUtil jsScriptServiceUtil;
private final JSDependencyTracker jsDependencyTracker;
@ -76,14 +74,21 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
@Activate
public GraalJSScriptEngineFactory(final @Reference JSScriptServiceUtil jsScriptServiceUtil,
final @Reference JSDependencyTracker jsDependencyTracker, Map<String, Object> config) {
logger.debug("Loading GraalJSScriptEngineFactory");
this.jsDependencyTracker = jsDependencyTracker;
this.jsScriptServiceUtil = jsScriptServiceUtil;
modified(config);
this.configuration = new GraalJSScriptEngineConfiguration(config);
}
@Modified
protected void modified(Map<String, ?> config) {
configuration.modified(config);
}
@Override
public List<String> getScriptTypes() {
return scriptTypes;
return SCRIPT_TYPES;
}
@Override
@ -93,22 +98,15 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
@Override
public @Nullable ScriptEngine createScriptEngine(String scriptType) {
if (!scriptTypes.contains(scriptType)) {
if (!SCRIPT_TYPES.contains(scriptType)) {
return null;
}
return new DebuggingGraalScriptEngine<>(new OpenhabGraalJSScriptEngine(injectionEnabled,
injectionCachingEnabled, jsScriptServiceUtil, jsDependencyTracker));
return new DebuggingGraalScriptEngine<>(
new OpenhabGraalJSScriptEngine(configuration, jsScriptServiceUtil, jsDependencyTracker));
}
@Override
public @Nullable ScriptDependencyTracker getDependencyTracker() {
return jsDependencyTracker;
}
@Modified
protected void modified(Map<String, ?> config) {
this.injectionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_ENABLED), Boolean.class, true);
this.injectionCachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_CACHING_ENABLED),
Boolean.class, true);
}
}

View File

@ -13,6 +13,7 @@
package org.openhab.automation.jsscripting.internal;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.*;
import static org.openhab.core.automation.module.script.ScriptTransformationService.OPENHAB_TRANSFORMATION_SCRIPT;
import java.io.IOException;
import java.io.InputStream;
@ -53,6 +54,7 @@ import org.openhab.automation.jsscripting.internal.fs.PrefixedSeekableByteChanne
import org.openhab.automation.jsscripting.internal.fs.ReadOnlySeekableByteArrayChannel;
import org.openhab.automation.jsscripting.internal.fs.watch.JSDependencyTracker;
import org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable;
import org.openhab.automation.jsscripting.internal.scriptengine.helper.LifecycleTracker;
import org.openhab.core.automation.module.script.ScriptExtensionAccessor;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.QuantityType;
@ -98,6 +100,7 @@ public class OpenhabGraalJSScriptEngine
}
}
private static final String OPENHAB_JS_INJECTION_CODE = "Object.assign(this, require('openhab'));";
private static final String EVENT_CONVERSION_CODE = "const event = (typeof this.rules?._getTriggeredData === 'function') ? rules._getTriggeredData(ctx, true) : this.event";
private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
/** Shared Polyglot {@link Engine} across all instances of {@link OpenhabGraalJSScriptEngine} */
@ -137,24 +140,24 @@ public class OpenhabGraalJSScriptEngine
/** {@link Lock} synchronization of multi-thread access */
private final Lock lock = new ReentrantLock();
private final JSRuntimeFeatures jsRuntimeFeatures;
private final LifecycleTracker lifecycleTracker = new LifecycleTracker();
private final GraalJSScriptEngineConfiguration configuration;
// these fields start as null because they are populated on first use
private @Nullable Consumer<String> scriptDependencyListener;
private String engineIdentifier; // this field is very helpful for debugging, please do not remove it
private String engineIdentifier = "<uninitialized>";
private boolean initialized = false;
private final boolean injectionEnabled;
private final boolean injectionCachingEnabled;
private boolean closed = false;
/**
* Creates an implementation of ScriptEngine {@code (& Invocable)}, wrapping the contained engine,
* that tracks the script lifecycle and provides hooks for scripts to do so too.
*/
public OpenhabGraalJSScriptEngine(boolean injectionEnabled, boolean injectionCachingEnabled,
public OpenhabGraalJSScriptEngine(GraalJSScriptEngineConfiguration configuration,
JSScriptServiceUtil jsScriptServiceUtil, JSDependencyTracker jsDependencyTracker) {
super(null); // delegate depends on fields not yet initialised, so we cannot set it immediately
this.injectionEnabled = injectionEnabled;
this.injectionCachingEnabled = injectionCachingEnabled;
this.configuration = configuration;
this.jsRuntimeFeatures = jsScriptServiceUtil.getJSRuntimeFeatures(lock);
delegate = GraalJSScriptEngine.create(ENGINE, Context.newBuilder("js") //
@ -163,9 +166,12 @@ public class OpenhabGraalJSScriptEngine
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException {
Consumer<String> localScriptDependencyListener = scriptDependencyListener;
if (localScriptDependencyListener != null) {
localScriptDependencyListener.accept(path.toString());
if (configuration.isDependencyTrackingEnabled()
&& path.startsWith(GraalJSScriptEngineFactory.JS_LIB_PATH)) {
Consumer<String> localScriptDependencyListener = scriptDependencyListener;
if (localScriptDependencyListener != null) {
localScriptDependencyListener.accept(path.toString());
}
}
if (path.toString().endsWith(".js")) {
@ -244,10 +250,10 @@ public class OpenhabGraalJSScriptEngine
protected void beforeInvocation() {
super.beforeInvocation();
logger.debug("Initializing GraalJS script engine...");
logger.debug("Initializing GraalJS script engine '{}' ...", engineIdentifier);
lock.lock();
logger.debug("Lock acquired before invocation.");
logger.debug("Lock acquired before invocation for engine '{}'.", engineIdentifier);
if (initialized) {
return;
@ -275,12 +281,13 @@ public class OpenhabGraalJSScriptEngine
.getAttribute(CONTEXT_KEY_DEPENDENCY_LISTENER);
if (localScriptDependencyListener == null) {
logger.warn(
"Failed to retrieve script script dependency listener from engine bindings. Script dependency tracking will be disabled.");
"Failed to retrieve script dependency listener from engine bindings. Script dependency tracking will be disabled for engine '{}'.",
engineIdentifier);
}
scriptDependencyListener = localScriptDependencyListener;
ScriptExtensionModuleProvider scriptExtensionModuleProvider = new ScriptExtensionModuleProvider(
scriptExtensionAccessor, lock);
scriptExtensionAccessor, lock, lifecycleTracker);
// Wrap the "require" function to also allow loading modules from the ScriptExtensionModuleProvider
Function<Function<Object[], Object>, Function<String, Object>> wrapRequireFn = originalRequireFn -> moduleName -> scriptExtensionModuleProvider
@ -291,34 +298,54 @@ public class OpenhabGraalJSScriptEngine
// Injections into the JS runtime
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> {
logger.debug("Injecting {} into the JS runtime...", key);
logger.debug("Injecting {} into the context of engine '{}' ...", key, engineIdentifier);
delegate.put(key, obj);
});
initialized = true;
try {
logger.debug("Evaluating cached global script...");
logger.debug("Evaluating cached global script for engine '{}' ...", engineIdentifier);
delegate.getPolyglotContext().eval(GLOBAL_SOURCE);
if (this.injectionEnabled) {
if (this.injectionCachingEnabled) {
logger.debug("Evaluating cached openhab-js injection...");
if (configuration.isInjectionEnabledForAllScripts()
|| (isUiBasedScript() && configuration.isInjectionEnabledForUiBasedScript())
|| (isTransformationScript() && configuration.isInjectionEnabledForTransformations())) {
if (configuration.isInjectionCachingEnabled()) {
logger.debug("Evaluating cached openhab-js injection for engine '{}' ...", engineIdentifier);
delegate.getPolyglotContext().eval(OPENHAB_JS_SOURCE);
} else {
logger.debug("Evaluating openhab-js injection from the file system...");
logger.debug("Evaluating openhab-js injection from the file system for engine '{}' ...",
engineIdentifier);
eval(OPENHAB_JS_INJECTION_CODE);
}
}
logger.debug("Successfully initialized GraalJS script engine.");
logger.debug("Successfully initialized GraalJS script engine '{}'.", engineIdentifier);
} catch (ScriptException e) {
logger.error("Could not inject global script", e);
}
}
@Override
protected String onScript(String script) {
if (isUiBasedScript() && configuration.isWrapperEnabled()) {
logger.debug("Wrapping script for engine '{}' ...", engineIdentifier);
String eventConversionScript = "";
if (configuration.isEventConversionEnabled()) {
eventConversionScript = EVENT_CONVERSION_CODE + System.lineSeparator();
}
return "(function() {" + System.lineSeparator() + eventConversionScript + script + System.lineSeparator()
+ "})()";
}
return super.onScript(script);
}
@Override
protected Object afterInvocation(Object obj) {
lock.unlock();
logger.debug("Lock released after invocation.");
logger.debug("Lock released after invocation for engine '{}'.", engineIdentifier);
return super.afterInvocation(obj);
}
@ -329,8 +356,56 @@ public class OpenhabGraalJSScriptEngine
}
@Override
public void close() {
jsRuntimeFeatures.close();
public void close() throws Exception {
if (closed) {
logger.debug("Engine '{}' is already disposed and closed.", engineIdentifier);
return;
}
lock.lock();
try {
try {
jsRuntimeFeatures.close();
this.lifecycleTracker.dispose();
} finally {
logger.debug("Engine '{}' disposed.", engineIdentifier);
super.close();
logger.debug("Engine '{}' closed.", engineIdentifier);
}
} finally {
closed = true;
lock.unlock();
}
}
/**
* Tests if the current script is a UI-based script, i.e. it is neither loaded from a file nor a transformation.
*
* @return true if the script is UI-based, false otherwise
*/
private boolean isUiBasedScript() {
ScriptContext ctx = delegate.getContext();
if (ctx == null) {
logger.warn("Failed to retrieve script context from engine '{}'.", engineIdentifier);
return false;
}
return ctx.getAttribute("javax.script.filename") == null
&& !engineIdentifier.startsWith(OPENHAB_TRANSFORMATION_SCRIPT);
}
/**
* Tests if the current script is a transformation script, i.e. it is created from the script transformation
* service.
*
* @return true if the script is a transformation script, false otherwise
*/
private boolean isTransformationScript() {
ScriptContext ctx = delegate.getContext();
if (ctx == null) {
logger.warn("Failed to retrieve script context from engine '{}'.", engineIdentifier);
return false;
}
return engineIdentifier.startsWith(OPENHAB_TRANSFORMATION_SCRIPT);
}
/**
@ -371,7 +446,7 @@ public class OpenhabGraalJSScriptEngine
@Override
public void lock() {
lock.lock();
logger.debug("Lock acquired.");
logger.debug("Lock acquired for engine '{}'.", engineIdentifier);
}
@Override
@ -392,7 +467,7 @@ public class OpenhabGraalJSScriptEngine
@Override
public void unlock() {
lock.unlock();
logger.debug("Lock released.");
logger.debug("Lock released for engine '{}'.", engineIdentifier);
}
@Override

View File

@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.openhab.automation.jsscripting.internal.scriptengine.helper.LifecycleTracker;
import org.openhab.automation.jsscripting.internal.threading.ThreadsafeWrappingScriptedAutomationManagerDelegate;
import org.openhab.core.automation.module.script.ScriptExtensionAccessor;
import org.openhab.core.automation.module.script.rulesupport.shared.ScriptedAutomationManager;
@ -30,22 +31,25 @@ import org.openhab.core.automation.module.script.rulesupport.shared.ScriptedAuto
* Class providing script extensions via CommonJS modules (with module name `@runtime`).
*
* @author Jonathan Gilbert - Initial contribution
* @author Florian Hotze - Pass in lock object for multi-thread synchronization; Switch to {@link Lock} for multi-thread
* synchronization
* @author Florian Hotze - Pass in a lock object for multi-thread synchronisation
* @author Florian Hotze - Switch to {@link Lock} for multi-thread synchronisation
* @author Florian Hotze - Overwrite lifecycleTracker with our own implementation
*/
@NonNullByDefault
public class ScriptExtensionModuleProvider {
private static final String RUNTIME_MODULE_PREFIX = "@runtime";
private static final String DEFAULT_MODULE_NAME = "Defaults";
private final Lock lock;
private final LifecycleTracker lifecycleTracker;
private final ScriptExtensionAccessor scriptExtensionAccessor;
public ScriptExtensionModuleProvider(ScriptExtensionAccessor scriptExtensionAccessor, Lock lock) {
public ScriptExtensionModuleProvider(ScriptExtensionAccessor scriptExtensionAccessor, Lock lock,
LifecycleTracker lifecycleTracker) {
this.scriptExtensionAccessor = scriptExtensionAccessor;
this.lock = lock;
this.lifecycleTracker = lifecycleTracker;
}
public ModuleLocator locatorFor(Context ctx, String engineIdentifier) {
@ -68,6 +72,7 @@ public class ScriptExtensionModuleProvider {
if (DEFAULT_MODULE_NAME.equals(name)) {
symbols = scriptExtensionAccessor.findDefaultPresets(scriptIdentifier);
symbols.put("lifecycleTracker", lifecycleTracker);
} else {
symbols = scriptExtensionAccessor.findPreset(name, scriptIdentifier);
}
@ -102,9 +107,9 @@ public class ScriptExtensionModuleProvider {
Map<String, Object> rv = new HashMap<>(values);
for (Map.Entry<String, Object> entry : rv.entrySet()) {
if (entry.getValue() instanceof ScriptedAutomationManager) {
entry.setValue(new ThreadsafeWrappingScriptedAutomationManagerDelegate(
(ScriptedAutomationManager) entry.getValue(), lock));
if (entry.getValue() instanceof ScriptedAutomationManager scriptedAutomationManager) {
entry.setValue(
new ThreadsafeWrappingScriptedAutomationManagerDelegate(scriptedAutomationManager, lock));
}
}

View File

@ -33,7 +33,7 @@ public abstract class DelegatingScriptEngineWithInvocableAndCompilableAndAutoclo
implements ScriptEngine, Invocable, Compilable, AutoCloseable {
protected T delegate;
public DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable(T delegate) {
protected DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable(T delegate) {
this.delegate = delegate;
}

View File

@ -33,26 +33,51 @@ import javax.script.ScriptException;
public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable<T extends ScriptEngine & Invocable & Compilable & AutoCloseable>
extends DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable<T> {
public InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable(T delegate) {
protected InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable(T delegate) {
super(delegate);
}
/**
* Hook method to be called before the invocation of any method on the script engine.
*/
protected void beforeInvocation() {
}
/**
* Hook method to be called when a string script is about to be evaluated.
*
* @param script the script to be evaluated
* @return the modified script to be evaluated instead, or the original script
*/
protected String onScript(String script) {
return script;
}
/**
* Hook method to be called after the invocation of any method on the script engine.
*
* @param obj the result of the invocation
* @return the result to be returned instead, or the original result
*/
protected Object afterInvocation(Object obj) {
return obj;
}
/**
* Hook method to be called after a {@link ScriptException} or other exception is thrown during invocation.
*
* @param e the exception that was thrown
* @return the exception to be thrown instead, or the original exception
*/
protected Exception afterThrowsInvocation(Exception e) {
return e;
}
@Override
public Object eval(String s, ScriptContext scriptContext) throws ScriptException {
public Object eval(String script, ScriptContext scriptContext) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s, scriptContext));
return afterInvocation(super.eval(onScript(script), scriptContext));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@ -73,10 +98,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object eval(String s) throws ScriptException {
public Object eval(String script) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s));
return afterInvocation(super.eval(onScript(script)));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@ -97,10 +122,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object eval(String s, Bindings bindings) throws ScriptException {
public Object eval(String script, Bindings bindings) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s, bindings));
return afterInvocation(super.eval(onScript(script), bindings));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@ -121,11 +146,11 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object invokeMethod(Object o, String s, Object... objects)
public Object invokeMethod(Object thiz, String name, Object... args)
throws ScriptException, NoSuchMethodException, NullPointerException, IllegalArgumentException {
try {
beforeInvocation();
return afterInvocation(super.invokeMethod(o, s, objects));
return afterInvocation(super.invokeMethod(thiz, name, args));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (NoSuchMethodException e) { // Make sure to unlock on exceptions from Invocable.invokeMethod to avoid
@ -141,11 +166,11 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object invokeFunction(String s, Object... objects)
public Object invokeFunction(String name, Object... args)
throws ScriptException, NoSuchMethodException, NullPointerException {
try {
beforeInvocation();
return afterInvocation(super.invokeFunction(s, objects));
return afterInvocation(super.invokeFunction(name, args));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (NoSuchMethodException e) { // Make sure to unlock on exceptions from Invocable.invokeFunction to avoid
@ -159,10 +184,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public CompiledScript compile(String s) throws ScriptException {
public CompiledScript compile(String script) throws ScriptException {
try {
beforeInvocation();
return (CompiledScript) afterInvocation(super.compile(s));
return (CompiledScript) afterInvocation(super.compile(onScript(script)));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2010-2025 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.automation.jsscripting.internal.scriptengine.helper;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* LifecycleTracker implementation
*
* <p>
* We can't use core's lifecycle tracker for JS Scripting, because its dispose hooks are called after the engine has
* been closed (which will not work).
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public class LifecycleTracker {
private List<Runnable> disposables = new ArrayList<>();
public void addDisposeHook(Runnable disposable) {
disposables.add(disposable);
}
public void dispose() {
for (Runnable disposable : disposables) {
disposable.run();
}
}
}

View File

@ -5,29 +5,67 @@
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="automation:jsscripting">
<parameter name="injectionEnabled" type="boolean" required="true">
<label>Use Built-in Global Variables</label>
<parameter-group name="environment">
<label>JavaScript Environment</label>
<description>This group defines JavaScript's environment.</description>
</parameter-group>
<parameter-group name="system">
<label>System Behaviour</label>
<description>This group defines JavaScript's system behaviour.</description>
</parameter-group>
<parameter name="injectionEnabledV2" type="integer" required="true" min="0" max="2" groupName="environment">
<label>Inject Global Variables from Helper Library</label>
<description><![CDATA[
Import all variables from the openHAB JavaScript library into all rules for common services like items, things, actions, log, etc... <br>
If disabled, the openHAB JavaScript library can be imported manually using "<i>require('openhab')</i>"
Import all variables from the openHAB JavaScript library for common services like items, things, actions, log, etc... <br>
If disabled, the openHAB JavaScript library can be imported manually using <code>require('openhab')</code>.
]]></description>
<options>
<option value="true">Use Built-in Variables</option>
<option value="false">Do Not Use Built-in Variables</option>
<option value="3">Auto injection for all scripts, including file-based scripts and transformations</option>
<option value="2">Auto injection for UI-based scripts and transformations</option>
<option value="1">Auto injection only for UI-based scripts (recommended)</option>
<option value="0">Disable auto-injection and import manually instead</option>
</options>
<default>true</default>
<default>3</default>
</parameter>
<parameter name="injectionCachingEnabled" type="boolean" required="true">
<parameter name="wrapperEnabled" type="boolean" required="true" groupName="environment">
<label>Wrap UI-based scripts in Self-Executing Function</label>
<description><![CDATA[
Wrapping UI-based scripts in a self-executing function allows the use of the <code>let</code> and <code>const</code> variable declarations,
as well as the use of <code>function</code> and <code>class</code> declarations.<br>
With this option enabled, you can also use <code>return</code> statements in your scripts to abort execution at any point.
]]></description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="eventConversionEnabled" type="boolean" required="true" groupName="environment">
<label>Convert Event from Java to JavaScript type in UI-based scripts</label>
<description><![CDATA[
Converting the event data from Java to JavaScript types in UI-based scripts allows working with event data in a native JS way without special handling for Java types.<br>
With this option enabled, the event data available in UI-based scripts is all JS types and the same as in file-based scripts.<br>
Please note that this option <strong>requires both auto-injection & wrapper enabled</strong> and only applies to UI-based scripts and does not affect file-based scripts.
]]></description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="injectionCachingEnabled" type="boolean" required="true" groupName="system">
<label>Cache openHAB JavaScript Library Injection</label>
<description><![CDATA[
Cache the openHAB JavaScript library injection for optimal performance.<br>
Disable this option to allow loading the library from the local user configuration directory "automation/js/node_modules". Disabling caching may increase script loading times, especially on less powerful systems.
]]></description>
<options>
<option value="true">Cache Library Injection</option>
<option value="false">Do Not Cache Library Injection</option>
</options>
<default>true</default>
</parameter>
<parameter name="dependencyTrackingEnabled" type="boolean" required="true" groupName="system">
<label>Enable Dependency Tracking</label>
<description>Dependency tracking allows your scripts to automatically reload when one of its dependencies is updated.
You may want to disable dependency tracking if you plan on editing or updating a shared library, but don't want all
your scripts to reload until you can test it. Please note that changing this setting only applies to scripts loaded
after the change.</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -5,11 +5,21 @@ addon.jsscripting.description = This adds a JS (ECMAScript-2024) script engine.
# add-on config
automation.config.jsscripting.dependencyTrackingEnabled.label = Enable Dependency Tracking
automation.config.jsscripting.dependencyTrackingEnabled.description = Dependency tracking allows your scripts to automatically reload when one of its dependencies is updated. You may want to disable dependency tracking if you plan on editing or updating a shared library, but don't want all your scripts to reload until you can test it. Please note that changing this setting only applies to scripts loaded after the change.
automation.config.jsscripting.eventConversionEnabled.label = Convert Event from Java to JavaScript type in UI-based scripts
automation.config.jsscripting.eventConversionEnabled.description = Converting the event data from Java to JavaScript types in UI-based scripts allows working with event data in a native JS way without special handling for Java types.<br> With this option enabled, the event data available in UI-based scripts is all JS types and the same as in file-based scripts.<br> Please note that this option <strong>requires both auto-injection & wrapper enabled</strong> and only applies to UI-based scripts and does not affect file-based scripts.
automation.config.jsscripting.group.environment.label = JavaScript Environment
automation.config.jsscripting.group.environment.description = This group defines JavaScript's environment.
automation.config.jsscripting.group.system.label = System Behaviour
automation.config.jsscripting.group.system.description = This group defines JavaScript's system behaviour.
automation.config.jsscripting.injectionCachingEnabled.label = Cache openHAB JavaScript Library Injection
automation.config.jsscripting.injectionCachingEnabled.description = Cache the openHAB JavaScript library injection for optimal performance.<br> Disable this option to allow loading the library from the local user configuration directory "automation/js/node_modules". Disabling caching may increase script loading times, especially on less powerful systems.
automation.config.jsscripting.injectionCachingEnabled.option.true = Cache Library Injection
automation.config.jsscripting.injectionCachingEnabled.option.false = Do Not Cache Library Injection
automation.config.jsscripting.injectionEnabled.label = Use Built-in Global Variables
automation.config.jsscripting.injectionEnabled.description = Import all variables from the openHAB JavaScript library into all rules for common services like items, things, actions, log, etc... <br> If disabled, the openHAB JavaScript library can be imported manually using "<i>require('openhab')</i>"
automation.config.jsscripting.injectionEnabled.option.true = Use Built-in Variables
automation.config.jsscripting.injectionEnabled.option.false = Do Not Use Built-in Variables
automation.config.jsscripting.injectionEnabledV2.label = Inject Global Variables from Helper Library
automation.config.jsscripting.injectionEnabledV2.description = Import all variables from the openHAB JavaScript library for common services like items, things, actions, log, etc... <br> If disabled, the openHAB JavaScript library can be imported manually using <code>require('openhab')</code>.
automation.config.jsscripting.injectionEnabledV2.option.3 = Auto injection for all scripts, including file-based scripts and transformations
automation.config.jsscripting.injectionEnabledV2.option.2 = Auto injection for UI-based scripts and transformations
automation.config.jsscripting.injectionEnabledV2.option.1 = Auto injection only for UI-based scripts (recommended)
automation.config.jsscripting.injectionEnabledV2.option.0 = Disable auto-injection and import manually instead
automation.config.jsscripting.wrapperEnabled.label = Wrap UI-based scripts in Self-Executing Function
automation.config.jsscripting.wrapperEnabled.description = Wrapping UI-based scripts in a self-executing function allows the use of the <code>let</code> and <code>const</code> variable declarations, as well as the use of <code>function</code> and <code>class</code> declarations.<br> With this option enabled, you can also use <code>return</code> statements in your scripts to abort execution at any point.

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.jsscriptingnashorn</artifactId>
@ -16,7 +16,7 @@
<properties>
<bnd.importpackage>jdk.dynalink.*;resolution:=optional</bnd.importpackage>
<asm.version>9.7.1</asm.version>
<asm.version>9.8</asm.version>
</properties>
<dependencies>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.jythonscripting</artifactId>

View File

@ -123,15 +123,15 @@ The visualization could be done by adding a persistence and use Grafana for exam
After you added a [Rule](https://www.openhab.org/docs/configuration/rules-dsl.html) with above trigger and action module and configured those, proceed with the following steps:
> *Notice:* A good starting point for the derivative time constant `kdTimeConstant` is the response time of the control loop.
> **Notice:** A good starting point for the derivative time constant `kdTimeConstant` is the response time of the control loop.
E.g. the time it takes from opening the heater valve and seeing an effect of the measured temperature.
1. Set `kp`, `ki` and `kd` to 0
2. Increase `kp` until the system starts to oscillate (continuous over- and undershoot)
3. Decrease `kp` a bit, that the system doesn't oscillate anymore
4. Repeat the two steps for the `ki` parameter (keep `kp` set)
5. Repeat the two steps for the `kd` parameter (keep `kp` and `ki` set)
6. As the D-part acts as a damper, you should now be able to increase `kp` and `ki` further without resulting in oscillations
1. Increase `kp` until the system starts to oscillate (continuous over- and undershoot)
1. Decrease `kp` a bit, that the system doesn't oscillate anymore
1. Repeat the two steps for the `ki` parameter (keep `kp` set)
1. Repeat the two steps for the `kd` parameter (keep `kp` and `ki` set)
1. As the D-part acts as a damper, you should now be able to increase `kp` and `ki` further without resulting in oscillations
After each modification of above parameters, test the system response by introducing a setpoint deviation (error).
This can be done either by changing the setpoint (e.g. 20°C -> 25°C) or by forcing the measured value to change (e.g. by opening a window).

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.pidcontroller</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.pwm</artifactId>

View File

@ -57,7 +57,6 @@ class Test:
::: tip Note
By default, the scope, Registry and logger is automatically imported for UI based rules
:::
## `PY` Transformation
@ -74,8 +73,8 @@ Use Python Scripting as script transformation by:
"String has " + str(len(input)) + " characters"
```
or
or
```python
def calc(input):
if input is None:
@ -85,8 +84,8 @@ Use Python Scripting as script transformation by:
calc(input)
```
2. Using `PY(<scriptname>.py):%s` as Item state transformation.
3. Passing parameters is also possible by using a URL like syntax: `PY(<scriptname>.py?arg=value)`.
1. Using `PY(<scriptname>.py):%s` as Item state transformation.
1. Passing parameters is also possible by using a URL like syntax: `PY(<scriptname>.py?arg=value)`.
Parameters are injected into the script and can be referenced like variables.
Simple transformations can also be given as an inline script: `PY(|...)`, e.g. `PY(|"String has " + str(len(input)) + "characters")`.
@ -96,7 +95,7 @@ It should start with the `|` character, quotes within the script may need to be
By default, the scope, Registry and logger is automatically imported for `PY` Transformation scripts
:::
## Examples
## Examples
### Simple rule
@ -139,7 +138,7 @@ class Test4:
if Registry.getItem("Item2").postUpdateIfDifferent(scope.OFF):
self.logger.info("Item2 was updated")
```
### Query thing status info
```python
@ -189,7 +188,7 @@ print("error message", file=sys.stderr)
```
2. using the logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting"
1. using the logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting"
```python
from openhab import logging
@ -199,7 +198,7 @@ logging.info("info message")
logging.error("error message")
```
3. using the rule based logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting.<RuleClassName>"
1. using the rule based logging module. Here you get a logging object, already initialized with the prefix "org.openhab.automation.pythonscripting.\<RuleClassName>"
```python
from openhab import rule
@ -215,7 +214,7 @@ class Test:
### decorator @rule
The decorator will register the decorated class as a rule.
The decorator will register the decorated class as a rule.
It will wrap and extend the class with the following functionalities
- Register the class or function as a rule
@ -237,7 +236,7 @@ class Test:
self.logger.info("Rule 3 was triggered")
```
```
```text
2025-01-09 09:35:11.002 [INFO ] [tomation.pythonscripting.demo1.Test2] - Rule executed in 0.1 ms [Item: Item1]
2025-01-09 09:35:15.472 [INFO ] [tomation.pythonscripting.demo1.Test1] - Rule executed in 0.1 ms [Other: TimerEvent]
```
@ -343,7 +342,6 @@ from scope import osgi
Additionally you can import all Java classes from 'org.openhab' package like
```python
from org.openhab.core import OpenHAB
@ -376,7 +374,7 @@ print(str(OpenHAB.getVersion()))
| Things | see [openHAB Things API](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/things) | |
| Transformation | see [openHAB Transformation API](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/transformation) | |
| Voice | see [openHAB Voice API](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/voice) | |
| NotificationAction | | e.g. NotificationAction.sendNotification("test@test.org", "Window is open") |
| NotificationAction | | e.g. NotificationAction.sendNotification("test\@test.org", "Window is open") |
### module openhab.triggers
@ -398,7 +396,7 @@ print(str(OpenHAB.getVersion()))
| TimeOfDayTrigger | TimeOfDayTrigger(time, trigger_name=None) | |
| DateTimeTrigger | DateTimeTrigger(cron_expression, trigger_name=None) | |
| PWMTrigger | PWMTrigger(cron_expression, trigger_name=None) | |
| GenericEventTrigger | GenericEventTrigger(event_source, event_types, event_topic="*/*", trigger_name=None) | |
| GenericEventTrigger | GenericEventTrigger(event_source, event_types, event_topic="\*/\*", trigger_name=None) | |
| ItemEventTrigger | ItemEventTrigger(event_types, item_name=None, trigger_name=None) | |
| ThingEventTrigger | ThingEventTrigger(event_types, thing_uid=None, trigger_name=None) | |
| | | |
@ -409,7 +407,7 @@ print(str(OpenHAB.getVersion()))
## Classes
### class Registry
### class Registry
| Function | Usage | Return Value |
| ------------------------ | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
@ -424,8 +422,7 @@ print(str(OpenHAB.getVersion()))
| addItem | addItem(item_config) | [Item](#class-item) or [GroupItem](#class-groupitem) |
| safeItemName | safeItemName(item_name) | |
### class Item
### class Item
Item is a wrapper around [openHAB Item](https://www.openhab.org/javadoc/latest/org/openhab/core/items/item) with additional functionality.
@ -439,11 +436,11 @@ Item is a wrapper around [openHAB Item](https://www.openhab.org/javadoc/latest/o
| getSemantic | getSemantic() | [ItemSemantic](#class-itemsemantic) |
| <...> | see [openHAB Item API](https://www.openhab.org/javadoc/latest/org/openhab/core/items/item) | |
### class GroupItem
### class GroupItem
GroupItem is an extended [Item](#class-item) which wraps results from getAllMembers & getMembers into [Items](#class-item)
### class ItemPersistence
### class ItemPersistence
ItemPersistence is a wrapper around [openHAB PersistenceExtensions](https://www.openhab.org/javadoc/latest/org/openhab/core/persistence/extensions/persistenceextensions). The parameters 'item' and 'serviceId', as part of the Wrapped Java API, are not needed, because they are inserted automatically.
@ -453,7 +450,7 @@ ItemPersistence is a wrapper around [openHAB PersistenceExtensions](https://www.
| getStableState | getStableState(time_slot, end_time = None) | Average calculation which takes into account the values depending on their duration |
| <...> | see [openHAB PersistenceExtensions API](https://www.openhab.org/javadoc/latest/org/openhab/core/persistence/extensions/persistenceextensions) | |
### class ItemSemantic
### class ItemSemantic
ItemSemantic is a wrapper around [openHAB Semantics](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/semantics). The parameters 'item', as part of the Wrapped Java API, is not needed because it is inserted automatically.
@ -461,23 +458,23 @@ ItemSemantic is a wrapper around [openHAB Semantics](https://www.openhab.org/jav
| ------------------------ | ------------------------------------------------------------------------------------- |
| <...> | see [openHAB Semantics API](https://www.openhab.org/javadoc/latest/org/openhab/core/model/script/actions/semantics) |
### class Thing
### class Thing
Thing is a wrapper around [openHAB Thing](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/thing).
Thing is a wrapper around [openHAB Thing](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/thing).
| Function | Usage |
| ------------------------ | ------------------------------------------------------------------------------------- |
| <...> | see [openHAB Thing API](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/thing) |
### class Channel
### class Channel
Channel is a wrapper around [openHAB Channel](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/type/channelgrouptype).
Channel is a wrapper around [openHAB Channel](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/type/channelgrouptype).
| Function | Usage |
| ------------------------ | ------------------------------------------------------------------------------------- |
| <...> | see [openHAB Channel API](https://www.openhab.org/javadoc/latest/org/openhab/core/thing/type/channelgrouptype) |
### class Timer
### class Timer
| Function | Usage | Description |
| ------------------------ | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
@ -578,7 +575,7 @@ The folder "conf/automation/python/" must be writeable by openHAB.
#### Failed to inject import wrapper
The reading the Python source file "conf/automation/python/lib/openhab/__wrapper__.py" failed.
The reading the Python source file "conf/automation/python/lib/openhab/\_\_wrapper\_\_py" failed.
This could either a permission/owner problem or a problem during deployment of the helper libs.
You should check that this file exists and it is readable by openHAB.
@ -586,4 +583,4 @@ You should also check your logs for a message related to the helper lib deployme
### Limitations
- GraalPy can't handle arguments in constructors of Java objects. Means you can't instantiate a Java object in Python with a parameter. https://github.com/oracle/graalpython/issues/367
- GraalPy can't handle arguments in constructors of Java objects. Means you can't instantiate a Java object in Python with a parameter. <https://github.com/oracle/graalpython/issues/367>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.automation.pythonscripting</artifactId>
@ -15,7 +15,6 @@
<name>openHAB Add-ons :: Bundles :: Automation :: Python Scripting</name>
<properties>
<graalpy.version>24.2.1</graalpy.version>
<helperlib.version>v1.0.0</helperlib.version>
</properties>
@ -94,6 +93,13 @@
<pmdFilter>${project.basedir}/suppressions.properties</pmdFilter>
</configuration>
</plugin>
<!-- workaround for failing clean of checked out git repo breaking the Windows build, #19250 -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<force>true</force>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -38,7 +38,6 @@ public final class GraalPythonScriptEngineFactory implements ScriptEngineFactory
private static final String[] EXTENSIONS = { "py" };
public GraalPythonScriptEngineFactory() {
super();
this.userDefinedEngine = null;
defaultEngine = new WeakReference<>(createDefaultEngine());

View File

@ -30,6 +30,7 @@ Background discovery is not supported.
### Hub
The hub offers two optional configuration parameters:
| Parameter | Description |
|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| host | The URL to reach the hub. The hub makes itself known through mDNS as `LCM1.local` and the host parameter defaults to this value. As long as the openHAB server and the hub are on the same broadcast domain for mDNS the host parameter doesn't need to be specified. |
@ -38,6 +39,7 @@ The hub offers two optional configuration parameters:
### Devices
All devices share one required paramenter:
| Parameter | Description |
|-----------|--------------------------------------------------------------------------------|
| zoneId | The zone ID that is assigned by the hub to each device as a unique identifier. |

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.adorne</artifactId>

View File

@ -8,13 +8,13 @@ This binding provides information about the upcoming waste collection dates for
## Discovery
Discovery is not possible, due some form input values from the website above are required.
Discovery is not possible, due to some form input values from the website above are required.
## Thing Configuration
For configuration of the **collectionSchedule** thing, you need the form inputs from the aha collections schedule web page. Follow the steps below to get the required configuration parameters from the form input values.
1. Open [aha Abfuhrkalender](https://www.aha-region.de/abholtermine/abfuhrkalender) in your favorite brower with developer-console.
1. Open [aha Abfuhrkalender](https://www.aha-region.de/abholtermine/abfuhrkalender) in your favorite browser with developer-console.
1. Open the developer console and switch to network tab (for example press F12 in chrome / edge / firefox).
1. Fill in the form: Select your commune, Street and house number and hit "Suchen".
1. Select the first request to [https://www.aha-region.de/abholtermine/abfuhrkalender](https://www.aha-region.de/abholtermine/abfuhrkalender) (see first screenshot below)
@ -46,7 +46,7 @@ The thing **aha Waste Collection Schedule** provides four channels for the upcom
| channel | type | description |
|----------|--------|------------------------------|
| generalWaste | DateTime | Next collection day for general waste |
| leightweightPackaging | DateTime | Next collection day for leightweight packaging |
| leightweightPackaging | DateTime | Next collection day for lightweight packaging |
| bioWaste | DateTime | Next collection day for bio waste |
| paper | DateTime | Next collection day for paper |
@ -62,7 +62,7 @@ wasteCollection.items
```java
DateTime collectionDay_generalWaste "Next general waste collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:generalWaste"}
DateTime collectionDay_leightweightPackaging "Next lightweight packaging collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:leightweightPackaging"}
DateTime collectionDay_lightweightPackaging "Next lightweight packaging collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:leightweightPackaging"}
DateTime collectionDay_bioWaste "Next bio waste collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:bioWaste"}
DateTime collectionDay_paper "Next paper collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:paper"}
```

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.ahawastecollection</artifactId>

View File

@ -2,9 +2,9 @@
AirGradient provides open source and open hardware air quality monitors.
This binding reads air quality data from the AirGradient (https://www.airgradient.com/) API.
This binding reads air quality data from the [AirGradient](https://www.airgradient.com/) API.
This API is documented at https://api.airgradient.com/public/docs/api/v1/
This API is documented at the [AirGradient API documentation](https://api.airgradient.com/public/docs/api/v1/).
## Supported Things
@ -30,31 +30,31 @@ The binding will adapt to the content type of the returned content to support di
| Name | Hostname | Content-Type | Parser |
|-------------------|-----------------------------------------------------------------|------------------------------|--------|
| API | Hostnames without any path (e.g., https://api.airgradient.com/) | application/json | JSON parser for the AirGradient API, correct paths will be appended to the calls |
| Local OpenMetrics | Hostnames with path (e.g., http://192.168.x.x/metrics) | application/openmetrics-text | OpenMetrics parser |
| Local Web | Hostnames with path (e.g., http://192.168.x.x/measures/current) | application/json | JSON parser for the AirGradient API, as if you returned the value of sendToServer() payload |
| Local Prometheus | Hostnames with path (e.g., http://192.168.x.x/measures) | text/plain | Prometheus parser for [Prometheus format](https://prometheus.io/docs/instrumenting/exposition_formats/) |
| API | Hostnames without any path (e.g., `https://api.airgradient.com/`) | application/json | JSON parser for the AirGradient API, correct paths will be appended to the calls |
| Local OpenMetrics | Hostnames with path (e.g., `http://192.168.x.x/metrics`) | application/openmetrics-text | OpenMetrics parser |
| Local Web | Hostnames with path (e.g., `http://192.168.x.x/measures/current`) | application/json | JSON parser for the AirGradient API, as if you returned the value of sendToServer() payload |
| Local Prometheus | Hostnames with path (e.g., `http://192.168.x.x/measures`) | text/plain | Prometheus parser for [Prometheus format](https://prometheus.io/docs/instrumenting/exposition_formats/) |
### AirGradient API
The connection to the API needs setup and configuration
1. Log in to the AirGradient Dashboard: https://app.airgradient.com/dashboard
2. Navigate to Place->Connectivity Settings from the upper left hamburger menu.
3. Enable API access, and take a copy of the Token, which will be used in the token setting to configure the connection to the API.
1. Log in to the [AirGradient Dashboard](https://app.airgradient.com/dashboard)
1. Navigate to Place->Connectivity Settings from the upper left hamburger menu.
1. Enable API access, and take a copy of the Token, which will be used in the token setting to configure the connection to the API.
To add a location, you need to know the location ID. To get the location ID, you
1. Log in to the AirGradient Dashboard: https://app.airgradient.com/dashboard
2. Navigate to Locations from the upper left hamburger menu.
3. Here you will find a list of all of your sensors, with a location ID in the left column. Use that id when you add new Location things.
1. Log in to the [AirGradient Dashboard](https://app.airgradient.com/dashboard)
1. Navigate to Locations from the upper left hamburger menu.
1. Here you will find a list of all of your sensors, with a location ID in the left column. Use that id when you add new Location things.
### `API` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|-----------------|---------|---------------------------------------|------------------------------|----------|----------|
| token | text | Token to access the device | N/A | yes | no |
| hostname | text | Hostname or IP address of the API | https://api.airgradient.com/ | no | yes |
| hostname | text | Hostname or IP address of the API | `https://api.airgradient.com/` | no | yes |
| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes |
### `Location` Thing Configuration
@ -65,7 +65,7 @@ To add a location, you need to know the location ID. To get the location ID, you
## Channels
For more information about the data in the channels, please refer to the models in https://api.airgradient.com/public/docs/api/v1/
For more information about the data in the channels, please refer to the [models in the AirGradient API documentation](https://api.airgradient.com/public/docs/api/v1/).
| Channel | Type | Read/Write | Description |
|--------------------|----------------------|------------|----------------------------------------------------------------------------------|
@ -103,7 +103,7 @@ These configuration settings needs AirGradient firmware on the sensor of version
## Full Example
### Thing Configuration
### Things Configuration
```java
Bridge airgradient:airgradient-api:home "My Home" [ token="abc123...." ] {

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.airgradient</artifactId>

View File

@ -6,14 +6,14 @@ You'll receive your API Key by mail.
## Supported Things
- `api`: bridge used to connect to the AirParif service. Provides some general informations for the whole area.
- `api`: bridge used to connect to the AirParif service. Provides some general information for the whole area.
- `location`: Presents the pollen and air quality information for a given location.
Of course, you can add multiple `location`s, e.g. for gathering pollen or air quality data for different locations.
## Discovery
Once your `api` bridge is created and configured with the API Key, a default `location` can be auto-discovered based on system location.
Once your `api` bridge is created and configured with the API Key, a default `location` can be auto-discovered based on system location.
It will be configured with the system location and detected department.
## Thing Configuration
@ -28,7 +28,7 @@ It will be configured with the system location and detected department.
| Name | Type | Description | Default | Required | Advanced |
|-----------------|---------|--------------------------------------------------------------------------------|---------|----------|----------|
| location | text | Geo coordinates to be considered (as <latitude>,<longitude>[,<altitude in m>]) | N/A | yes | no |
| location | text | Geo coordinates to be considered (as `{latitude},{longitude}[,{altitude in m}]`) | N/A | yes | no |
| department | text | Code of the department (two digits) (*) | N/A | yes | no |
(*) When auto-discovered, the department will be pre-filled based on the location and bounding limits defined in the internal department database.
@ -129,29 +129,29 @@ This binding has its own IconProvider and makes available the following list of
| Icon Name | Dynamic | Illustration |
|------------------------|---------|--------------|
| oh:airparif:aq | Yes | ![](doc/images/aq.svg) |
| oh:airparif:alder | Yes | ![](doc/images/alder.svg) |
| oh:airparif:ash | Yes | ![](doc/images/ash.svg) |
| oh:airparif:birch | Yes | ![](doc/images/birch.svg) |
| oh:airparif:chestnut | Yes | ![](doc/images/chestnut.svg) |
| oh:airparif:cypress | Yes | ![](doc/images/cypress.svg) |
| oh:airparif:grasses | Yes | ![](doc/images/grasses.svg) |
| oh:airparif:hazel | Yes | ![](doc/images/hazel.svg) |
| oh:airparif:hornbeam | Yes | ![](doc/images/hornbeam.svg) |
| oh:airparif:linden | Yes | ![](doc/images/linden.svg) |
| oh:airparif:oak | Yes | ![](doc/images/oak.svg) |
| oh:airparif:olive | Yes | ![](doc/images/olive.svg) |
| oh:airparif:plane | Yes | ![](doc/images/plane.svg) |
| oh:airparif:plantain | Yes | ![](doc/images/plantain.svg) |
| oh:airparif:aq | Yes | ![Air Quality](doc/images/aq.svg) |
| oh:airparif:alder | Yes | ![Alder](doc/images/alder.svg) |
| oh:airparif:ash | Yes | ![Ash](doc/images/ash.svg) |
| oh:airparif:birch | Yes | ![Birch](doc/images/birch.svg) |
| oh:airparif:chestnut | Yes | ![Chestnut](doc/images/chestnut.svg) |
| oh:airparif:cypress | Yes | ![Cypress](doc/images/cypress.svg) |
| oh:airparif:grasses | Yes | ![Grasses](doc/images/grasses.svg) |
| oh:airparif:hazel | Yes | ![Hazel](doc/images/hazel.svg) |
| oh:airparif:hornbeam | Yes | ![Hornbeam](doc/images/hornbeam.svg) |
| oh:airparif:linden | Yes | ![Linden](doc/images/linden.svg) |
| oh:airparif:oak | Yes | ![Oak](doc/images/oak.svg) |
| oh:airparif:olive | Yes | ![Olive](doc/images/olive.svg) |
| oh:airparif:plane | Yes | ![Plane](doc/images/plane.svg) |
| oh:airparif:plantain | Yes | ![Plantain](doc/images/plantain.svg) |
| oh:airparif:pollen | Yes | x |
| oh:airparif:poplar | Yes | ![](doc/images/poplar.svg) |
| oh:airparif:ragweed | Yes | ![](doc/images/ragweed.svg) |
| oh:airparif:rumex | Yes | ![](doc/images/rumex.svg) |
| oh:airparif:urticaceae | Yes | ![](doc/images/urticaceae.svg) |
| oh:airparif:willow | Yes | ![](doc/images/willow.svg) |
| oh:airparif:wormwood | Yes | ![](doc/images/wormwood.svg) |
| oh:airparif:poplar | Yes | ![Poplar](doc/images/poplar.svg) |
| oh:airparif:ragweed | Yes | ![Ragweed](doc/images/ragweed.svg) |
| oh:airparif:rumex | Yes | ![Rumex](doc/images/rumex.svg) |
| oh:airparif:urticaceae | Yes | ![Urticaceae](doc/images/urticaceae.svg) |
| oh:airparif:willow | Yes | ![Willow](doc/images/willow.svg) |
| oh:airparif:wormwood | Yes | ![Wormwood](doc/images/wormwood.svg) |
## Full Examplee
## Full Example
### Thing Configurationn

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.airparif</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.airq</artifactId>

View File

@ -54,7 +54,7 @@ This `stationId` can be found by using the following link:
### Thing properties
Once created, at first execution, the station's properties will be filled with informations gathered from the web service :
Once created, at first execution, the station's properties will be filled with information gathered from the web service:
- Nearest measuring station location
- Measuring station ID
@ -88,7 +88,7 @@ The AirQuality information that is retrieved for a given is available as these c
### Pollutants Channels Group
For each pollutant (PM25, PM10, O3, NO2, CO, SO2) , depending upon availability of the station,
you will be provided with the following informations
you will be provided with the following information.
| Channel ID | Item Type | Description |
|-----------------|----------------------|----------------------------------------------|
@ -96,7 +96,7 @@ you will be provided with the following informations
| index | Number | AQI Index of the single pollutant |
| alert-level | Number | Alert level associate to the index |
(*) The alert level is described by a color :
(*) The alert level is described by a color:
| Code | Color | Description |
|------|--------|--------------------------------|

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.airquality</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.airvisualnode</artifactId>

View File

@ -175,7 +175,7 @@ Thing config file example:
The `lrr` thing reports messages sent to a Long Range Radio (LRR) or emulated LRR device.
These are normally specifically formatted messages as described in the [SIA DC-05-1999.09](https://www.alarmdecoder.com/wiki/index.php/File:SIA-ContactIDCodes_Protocol.pdf) standard for Contact ID reporting.
They can also, depending on configuration, be other types of messages as described [here](https://www.alarmdecoder.com/wiki/index.php/LRR_Support).
They can also, depending on configuration, be other types of messages as described in the [AlarmDecoder LRR Support documentation](http://www.alarmdecoder.com/wiki/index.php/LRR_Support).
For panels that support multiple partitions, the partition for which a given lrr thing will receive messages can be defined.
- `partition` (default = 0) Partition for which to receive LRR events (0 = All)
@ -312,4 +312,4 @@ In other words: to get to a clean slate after an openHAB restart, close all door
## Reference Information
The protocol used to communicate with the Alarm Decoder is described [here](https://www.alarmdecoder.com/wiki/index.php/Protocol).
The protocol used to communicate with the Alarm Decoder is described in the [AlarmDecoder Protocol documentation](http://www.alarmdecoder.com/wiki/index.php/Protocol).

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.alarmdecoder</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.allplay</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amazondashbutton</artifactId>

View File

@ -104,7 +104,7 @@ With the possibility to control your lights you could do:
You must define an `account` (Bridge) before defining any other Thing can be used.
1. Create an Amazon `account` thing
1. Open the url `YOUR_OPENHAB/amazonechocontrol` in your browser (e.g. http://openhab:8080/amazonechocontrol/), click the link for your account thing and login.
1. Open the URL `YOUR_OPENHAB/amazonechocontrol` in your browser (e.g. `http://openhab:8080/amazonechocontrol/`), click the link for your account thing and login.
1. You should see now a message that the login was successful
1. If you encounter redirect/page refresh issues, enable two-factor authentication (2FA) on your Amazon account.
@ -119,7 +119,7 @@ You will find the required serial number in settings of the device in the Alexa
If you want to discover your smart home devices you need to activate it in the 'Amazon Account' thing.
Devices from other skills can be discovered too.
See section *Smart Home Devices* below for more information.
See section _Smart Home Devices_ below for more information.
## Configuration
@ -138,7 +138,6 @@ See section *Smart Home Devices* below for more information.
|-----------------|-----------|-------------|------------|--------------------------------------------------|
| `sendMessage` | String | W | account | Write Only! Sends a message to the Echo devices. |
### Thing Configuration
The `echo`, `echospot`, `echoshow` and `wha` have the same configuration:
@ -147,9 +146,9 @@ The `echo`, `echospot`, `echoshow` and `wha` have the same configuration:
|--------------------------|----------------------------------------------------|
| `serialNumber` | Serial number of the Amazon Echo in the Alexa app |
You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/amazonechocontrol/YOUR_ACCOUNT (e.g. http://openhab:8080/amazonechocontrol/account1).
You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/amazonechocontrol/YOUR_ACCOUNT (e.g. `http://openhab:8080/amazonechocontrol/account1`).
### Channels
### Echo Device Channels
| Channel Type ID | Item Type | Access Mode | Thing Type | Description |
|-----------------------|-------------|-------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@ -177,13 +176,13 @@ You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/
| startRoutine | String | W | echo, echoshow, echospot | Write Only! Type in what you normally say to Alexa without the preceding "Alexa," |
| musicProviderId | String | R/W | echo, echoshow, echospot | Current Music provider |
| playMusicVoiceCommand | String | W | echo, echoshow, echospot | Write Only! Voice command as text. E.g. 'Yesterday from the Beatles' |
| startCommand | String | W | echo, echoshow, echospot | Write Only! Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing.<FlahshbriefingDeviceID> (Note: The options are case sensitive) |
| startCommand | String | W | echo, echoshow, echospot | Write Only! Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing.{FlashbriefingDeviceID} (Note: The options are case sensitive) |
| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. See in the tutorial section to learn how its possible to set the title and turn off the sound. |
| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `<speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect></speak>` |
| textToSpeechVolume | Dimmer | R/W | echo, echoshow, echospot | Volume of the textToSpeech channel, if 0 the current volume will be used |
| textCommand | String | W | echo, echoshow, echospot | Write Only! Execute a text command (like a spoken text) |
| lastVoiceCommand | String | R | echo, echoshow, echospot | Last voice command spoken to the device. |
| lastSpokenText | String | R | echo, echoshow, echospot | Last spoken text from the device. (for example statements, answers and text to speeches) |
| lastSpokenText | String | R | echo, echoshow, echospot | Last spoken text from the device. (for example statements, answers and text to speeches) |
| mediaProgress | Dimmer | R/W | echo, echoshow, echospot | Media progress in percent |
| mediaProgressTime | Number:Time | R/W | echo, echoshow, echospot | Media play time |
| mediaLength | Number:Time | R | echo, echoshow, echospot | Media length |
@ -198,7 +197,7 @@ This can be used to call Alexa API from rules.
E.g. to read out the history call from an installation on openhab:8080 with an account named account1:
http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1
`http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1`
### Example
@ -338,18 +337,18 @@ The `flashbriefingprofile` thing helps you to overcome this limitation.
To set it up using managed (UI) configuration:
1. Use the app to create the flash-briefing configuration you want.
2. Start the discovery, you should see a new "flashbriefingprofile" thing. If this is not the case, a thing with the current configuration already exists.
3. Add that thing (you can use a custom name if you want).
4. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different).
1. Start the discovery, you should see a new "flashbriefingprofile" thing. If this is not the case, a thing with the current configuration already exists.
1. Add that thing (you can use a custom name if you want).
1. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different).
Textual configuration (untested, not recommended):
1. Add a new `flashbriefiungprofilething` to your `.things` file.
2. Use the app to create the flash-briefing configuration you want.
3. Send `ON` to the `save` channel of the thing you created in step 1.
4. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different).
1. Use the app to create the flash-briefing configuration you want.
1. Send `ON` to the `save` channel of the thing you created in step 1.
1. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different).
#### Channels
### Flash Briefing Profile Channels
| Channel Type ID | Item Type | Access Mode | Description |
|-----------------|-----------|:-----------:|-----------------------------------------------------------------------------------------------------------------------------------------------|
@ -360,7 +359,7 @@ Textual configuration (untested, not recommended):
**Attention:** Be careful when using the `save` channel.
Storing the same configuration to several things may result in unpredictable behavior.
### Example
### Flash Briefing Example
#### flashbriefings.things
@ -421,7 +420,7 @@ You can use that id if you want to define the thing in a file.
Discovered smart home devices show a `deviceIdentifierList` in their thing properties, containing one or more serial numbers.
You can check if any of these serial numbers is associated with another device and use this to identify devices with similar/same names.
### Channels
### Smart Home Device Channels
The channels of the smarthome devices will be generated at runtime.
Check in the UI thing configurations, which channels are created.
@ -451,13 +450,13 @@ Check in the UI thing configurations, which channels are created.
| contact | Contact | R | smartHomeDevice | A contact sensor OPEN if detected, CLOSED if NOT_DETECTED |
| geoLocation | Location | R | smartHomeDevice | The location (e.g. of a Tile) |
*Note* the channels of `smartHomeDevices` and `smartHomeDeviceGroup` will be created dynamically based on the capabilities reported by the Amazon server. This can take a little bit of time.
**Note:** the channels of `smartHomeDevices` and `smartHomeDeviceGroup` will be created dynamically based on the capabilities reported by the Amazon server. This can take a little bit of time.
The polling interval configured in the Account Thing to get the state is specified in minutes and has a minimum of 10. This means it takes up to 10 minutes to see the state of a channel. The reason for this low interval is, that the polling causes a big server load for the Smart Home Skills.
*Note*: The `color` channel is read-only by default because Alexa does only support setting colors by their name.
**Note:** The `color` channel is read-only by default because Alexa does only support setting colors by their name.
It has a configuration parameter `matchColors` which enables writing to that channel and tries to find the closes available color when sending a command to Alexa.
### Example
### Smart Home Example
#### smarthome.things
@ -520,19 +519,19 @@ Link the `geoLocation` channel of the Tile thing to a `Location` item named `Car
Add a second item of type `Number:Length` with the name `CarDistance` (adjust state description to your needs, e.g. miles or km as unit).
Create a rule that triggers on change of that item with the DSL script as action:
```
```javascript
var homeLocation = new PointType("50.273448, 8.409950")
CarDistance.postUpdate(homeLocation.distanceFrom(CarLocation.state as PointType).toString + " m")
```
## Advanced Feature Technically Experienced Users
## Advanced Features for Technically Experienced Users
The url <YOUR_OPENHAB>/amazonechocontrol/<YOUR_ACCOUNT>/PROXY/<API_URL> provides a proxy server with an authenticated connection to the Amazon Alexa server.
This can be used to call Alexa API from rules.
E.g. to read out the history call from an installation on openhab:8080 with an account named account1:
http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1
`http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1`
To resolve login problems the connection settings of an `account` thing can be reset via the karaf console.
The command `amazonechocontrol listAccounts` shows a list of all available `account` things.
@ -596,7 +595,6 @@ No specification uses the volume from the `textToSpeechVolume` channel.
Note: If you turn off the sound and Alexa is playing music, it will anyway turn down the volume for a moment. This behavior can not be changed.
```java
rule "Say welcome if the door opens"
when
@ -608,9 +606,9 @@ end
## Playing an alarm sound for 15 seconds with an openHAB rule if a door contact was opened
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
2) Write down the text in the square brackets. e.g. ECHO:system_alerts_repetitive01 for the nightstand sound
3) Create a rule for start playing the sound:
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
1. Write down the text in the square brackets. e.g. ECHO:system_alerts_repetitive01 for the nightstand sound
1. Create a rule for start playing the sound:
```java
var Timer stopAlarmTimer = null
@ -638,9 +636,9 @@ Note 2: The rule have no effect for your default alarm sound used in the Alexa a
### Play a spotify playlist if a switch was changed to on
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
2) Write down the text in the square brackets. e.g. SPOTIFY for the spotify music provider
3) Create a rule for start playing a song or playlist:
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
1. Write down the text in the square brackets. e.g. SPOTIFY for the spotify music provider
1. Create a rule for start playing a song or playlist:
```java
rule "Play a playlist on spotify if a switch was changed"
@ -656,8 +654,8 @@ Note: It is recommended to test the command send to play music command first wit
### Start playing weather/traffic/etc
1) Pick up one of the available commands: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing
2) Create a rule for start playing the information where you provide the command as string:
1. Pick up one of the available commands: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing
1. Create a rule for start playing the information where you provide the command as string:
```java
rule "Start wheater info"
@ -670,9 +668,9 @@ end
### Start playing a custom flashbriefing on a device
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
2) Write down the text in the square brackets. e.g. flashbriefing.flashbriefing1
2) Create a rule for start playing the information where you provide the command as string:
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
1. Write down the text in the square brackets. e.g. flashbriefing.flashbriefing1
1. Create a rule for start playing the information where you provide the command as string:
```java
rule "Start wheater info"
@ -696,7 +694,7 @@ The binding is tested with amazon.de, amazon.fr, amazon.it, amazon.com and amazo
The idea for writing this binding came from this blog: [https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html](https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html) (German).
Thank you Alex!
The technical information for the web socket connection to get live Alexa state updates cames from Ingo.
He has done the Alexa ioBroker implementation https://github.com/Apollon77
He has done the [Alexa ioBroker implementation](https://github.com/Apollon77)
Thank you Ingo!
## Trademark Disclaimer

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amazonechocontrol</artifactId>

View File

@ -1406,7 +1406,7 @@ public class Connection {
public List<MusicProviderTO> getMusicProviders() {
try {
return requestBuilder.get(getAlexaServer() + "/api/behaviors/entities?skillId=amzn1.ask.1p.music")
.withHeader("Routines-Version", "1.1.218665").syncSend(MusicProviderTO.LIST_TYPE_TOKEN);
.withHeader("Routines-Version", "3.0.264101").syncSend(MusicProviderTO.LIST_TYPE_TOKEN);
} catch (ConnectionException e) {
logger.warn("Failed to get music providers: {}", e.getMessage());
}

View File

@ -314,10 +314,12 @@ public class LoginData {
@Override
public boolean equals(@Nullable Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoginData loginData = (LoginData) o;
return Objects.equals(frc, loginData.frc) && Objects.equals(serial, loginData.serial)
&& Objects.equals(deviceId, loginData.deviceId) && Objects.equals(refreshToken, loginData.refreshToken)

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2010-2025 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.amazonechocontrol.internal.dto.response;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice;
/**
* The {@link EndPointItemTO} encapsulate the GSON data of a smarthome graphql query
*
* @author Leo Siepel - Initial contribution
*/
@NonNullByDefault
public class EndPointItemTO {
public @Nullable String endpointId;
public @Nullable String id;
public @Nullable String friendlyName;
public @Nullable JsonSmartHomeDevice legacyAppliance;
public @Nullable StringWrapper serialNumber;
public @Nullable String enablement;
public @Nullable StringWrapper model;
public @Nullable StringWrapper manufacturer;
public static class TextValue {
public @Nullable String text;
}
public static class StringWrapper {
public @Nullable TextValue value;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2025 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.amazonechocontrol.internal.dto.response;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice;
/**
* The {@link SmartHomeTO} encapsulate the GSON data of a smarthome graphql query
*
* @author Leo Siepel - Initial contribution
*/
@NonNullByDefault
public class SmartHomeTO {
public @Nullable Data data;
public static class Data {
public @Nullable Endpoints endpoints;
}
public static class Endpoints {
public @Nullable List<EndPointItemTO> items;
}
public List<JsonSmartHomeDevice> getLegacySmartHomeDevices() {
if (data instanceof Data data && data.endpoints != null && data.endpoints instanceof Endpoints endpoints
&& endpoints.items instanceof List<EndPointItemTO> items) {
return items.stream().map(item -> item.legacyAppliance).filter(java.util.Objects::nonNull)
.map(device -> (@NonNull JsonSmartHomeDevice) device).toList();
}
return List.of();
}
}

View File

@ -66,12 +66,12 @@ import org.openhab.binding.amazonechocontrol.internal.dto.response.BluetoothStat
import org.openhab.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordTO;
import org.openhab.binding.amazonechocontrol.internal.dto.response.ListItemTO;
import org.openhab.binding.amazonechocontrol.internal.dto.response.MusicProviderTO;
import org.openhab.binding.amazonechocontrol.internal.dto.response.SmartHomeTO;
import org.openhab.binding.amazonechocontrol.internal.dto.response.WakeWordTO;
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice;
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroups;
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice;
import org.openhab.binding.amazonechocontrol.internal.push.PushConnection;
import org.openhab.binding.amazonechocontrol.internal.smarthome.JsonNetworkDetails;
import org.openhab.binding.amazonechocontrol.internal.smarthome.SmartHomeDeviceStateGroupUpdateCalculator;
import org.openhab.binding.amazonechocontrol.internal.types.Notification;
import org.openhab.core.library.types.OnOffType;
@ -416,7 +416,7 @@ public class AccountHandler extends BaseBridgeHandler implements PushConnection.
if (!connection.isLoggedIn()) {
return;
}
logger.debug("refresh notifications {}", getThing().getUID().getAsString());
ZonedDateTime requestTime = ZonedDateTime.now();
List<Notification> notifications = connection.getNotifications().stream()
.map(n -> map(n, requestTime, ZonedDateTime.now())).filter(Objects::nonNull)
@ -709,28 +709,35 @@ public class AccountHandler extends BaseBridgeHandler implements PushConnection.
return List.of();
}
String jsonQuery = "{\"query\":\"query Endpoints{endpoints{items{endpointId id friendlyName displayCategories{primary{value}} legacyIdentifiers{dmsIdentifier{deviceType{type value{text}} deviceSerialNumber{type value{text}}}} legacyAppliance{applianceId applianceTypes endpointTypeId friendlyName friendlyDescription manufacturerName connectedVia modelName entityId actions mergedApplianceIds capabilities applianceNetworkState version isEnabled customerDefinedDeviceType customerPreference alexaDeviceIdentifierList aliases driverIdentity additionalApplianceDetails isConsentRequired applianceKey appliancePairs deduplicatedPairs entityPairs deduplicatedAliasesByEntityId relations} serialNumber{value{text}} enablement model{value{text}} manufacturer{value{text}} features{name operations{name}}}}}\"}";
try {
if (connection.isLoggedIn()) {
JsonNetworkDetails networkDetails = connection.getRequestBuilder()
.get(connection.getAlexaServer() + "/api/phoenix").syncSend(JsonNetworkDetails.class);
Object jsonObject = gson.fromJson(networkDetails.networkDetail, Object.class);
List<SmartHomeBaseDevice> smartHomeDevices = new ArrayList<>();
searchSmartHomeDevicesRecursive(jsonObject, smartHomeDevices);
SmartHomeTO networkDetails = connection.getRequestBuilder()
.post(connection.getAlexaServer() + "/nexus/v1/graphql").withContent(jsonQuery).withJson(true)
.syncSend(SmartHomeTO.class);
// create new id map
List<JsonSmartHomeDevice> smartHomeDevices = (networkDetails != null)
? networkDetails.getLegacySmartHomeDevices()
: List.of();
Map<String, SmartHomeBaseDevice> newJsonIdSmartHomeDeviceMapping = new HashMap<>();
for (SmartHomeBaseDevice smartHomeDevice : smartHomeDevices) {
for (JsonSmartHomeDevice smartHomeDevice : smartHomeDevices) {
String id = smartHomeDevice.findId();
if (id != null) {
newJsonIdSmartHomeDeviceMapping.put(id, smartHomeDevice);
}
}
// TODO previously `searchSmartHomeDevicesRecursive` was called here. Due to changes in the JSON
// structure, this may need to be re-implemented.
// JsonSmartHomeDevice are now directly accessible from the response, JsonSmartHomeGroups.SmartHomeGroup
// seem to be missing
jsonIdSmartHomeDeviceMapping = newJsonIdSmartHomeDeviceMapping;
// update handlers
smartHomeDeviceHandlers
.forEach(child -> child.setDeviceAndUpdateThingState(this, findSmartHomeDeviceJson(child)));
return smartHomeDevices;
return newJsonIdSmartHomeDeviceMapping.values().stream().toList();
}
} catch (ConnectionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());

View File

@ -144,14 +144,11 @@ public class EchoHandler extends BaseThingHandler {
@Override
public void initialize() {
Bridge bridge = this.getBridge();
if (bridge != null) {
account = (AccountHandler) bridge.getHandler();
}
if (account == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge handler not found.");
} else {
if (this.getBridge() instanceof Bridge bridge && bridge.getHandler() instanceof AccountHandler handler) {
account = handler;
updateStatus(ThingStatus.UNKNOWN);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge handler not found.");
}
lastCustomerHistoryRecordTimestamp = System.currentTimeMillis();
@ -175,13 +172,17 @@ public class EchoHandler extends BaseThingHandler {
@Override
public void dispose() {
stopCurrentNotification();
stopUpdateStateJob();
stopProgressTimer();
}
private void stopUpdateStateJob() {
ScheduledFuture<?> updateStateJob = this.updateStateJob;
this.updateStateJob = null;
if (updateStateJob != null) {
this.disableUpdate = false;
updateStateJob.cancel(false);
}
stopProgressTimer();
}
private void stopProgressTimer() {
@ -201,8 +202,7 @@ public class EchoHandler extends BaseThingHandler {
}
public String getSerialNumber() {
String id = (String) getConfig().get(DEVICE_PROPERTY_SERIAL_NUMBER);
return id != null ? id : "";
return Objects.requireNonNullElse((String) getConfig().get(DEVICE_PROPERTY_SERIAL_NUMBER), "");
}
@Override
@ -588,9 +588,9 @@ public class EchoHandler extends BaseThingHandler {
this.updateStateJob = scheduler.schedule(doRefresh, waitForUpdate, TimeUnit.MILLISECONDS);
}
} catch (ConnectionException e) {
logger.info("Failed to handle command '{}' to '{}'", command, channelUID);
logger.warn("Failed to handle command '{}' to '{}': {}", command, channelUID, e.getMessage(), e);
} catch (RuntimeException e) {
logger.warn("RuntimeException in handle command", e);
logger.warn("RuntimeException in handle command for channel '{}': {}", channelUID, e.getMessage(), e);
}
}
@ -648,7 +648,7 @@ public class EchoHandler extends BaseThingHandler {
}
}
} catch (ConnectionException e) {
logger.warn("Failed to update notification state");
logger.warn("Failed to update notification state: {}", e.getMessage(), e);
}
if (stopCurrentNotification) {
stopCurrentNotification();
@ -861,7 +861,7 @@ public class EchoHandler extends BaseThingHandler {
PlayerStateTO playerState = connection.getPlayerState(device);
updateMediaPlayerState(playerState.playerInfo, connection.isSequenceNodeQueueRunning(), 1000);
} catch (ConnectionException e) {
// ignored
logger.debug("Failed to update player state: {}", e.getMessage(), e);
}
// handle bluetooth
@ -1003,7 +1003,8 @@ public class EchoHandler extends BaseThingHandler {
PlayerStateTO playerState = connection.getPlayerState(device);
updateMediaPlayerState(playerState.playerInfo, connection.isSequenceNodeQueueRunning(), 1000);
}
} catch (ConnectionException ignored) {
} catch (ConnectionException e) {
logger.debug("Failed to refresh audio player state: {}", e.getMessage(), e);
}
});
}

View File

@ -56,7 +56,8 @@ public class Constants {
public static final ChannelTypeUID CHANNEL_TYPE_THERMOSTATMODE = new ChannelTypeUID(BINDING_ID, "thermostatMode");
public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_INDOOR_AIR_QUALITY = new ChannelTypeUID(BINDING_ID,
"indoorAirQuality");
public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_HUMIDITY = new ChannelTypeUID(BINDING_ID, "humidity");
public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_HUMIDITY = new ChannelTypeUID(BINDING_ID,
"relativeHumidity");
public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_PM25 = new ChannelTypeUID(BINDING_ID, "pm25");
public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_CARBON_MONOXIDE = new ChannelTypeUID(BINDING_ID,
"carbonMonoxide");

View File

@ -15,7 +15,6 @@ package org.openhab.binding.amazonechocontrol.internal.util;
import static org.eclipse.jetty.http.HttpHeader.*;
import static org.eclipse.jetty.http.HttpMethod.*;
import static org.eclipse.jetty.http.HttpStatus.*;
import static org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400;
import static org.eclipse.jetty.http.MimeTypes.Type.APPLICATION_JSON_UTF_8;
import static org.eclipse.jetty.http.MimeTypes.Type.FORM_ENCODED;
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.API_VERSION;
@ -256,20 +255,22 @@ public class HttpRequestBuilder {
if (returnType.getRawType().equals(HttpRequestBuilder.HttpResponse.class)) {
return (T) response;
}
String contentType = response.headers.get(CONTENT_TYPE);
if (!contentType.startsWith(MediaType.APPLICATION_JSON)) {
logger.debug("JSON conversion to {} was requested but the response has a Content-Type {}",
returnType.getType().getTypeName(), contentType);
}
try {
String contentType = response.headers.get(CONTENT_TYPE);
if (contentType == null) {
throw new JsonParseException("Response Content-Type header is missing");
}
T returnValue = gson.fromJson(response.content(), returnType);
// gson.fromJson is non-null if json is non-null and not empty
if (returnValue == null) {
if (!contentType.startsWith(MediaType.APPLICATION_JSON)) {
throw new JsonParseException("Response Content-Type is not JSON: " + contentType);
}
throw new JsonParseException("Empty result");
}
return returnValue;
} catch (JsonParseException e) {
logger.warn("Parsing json failed: {}", isJson, e);
logger.warn("Parsing json failed, exception: {}", e.getMessage());
throw e;
}
});
@ -420,10 +421,12 @@ public class HttpRequestBuilder {
public record HttpResponse(int statusCode, HttpFields headers, String content) {
@Override
public boolean equals(@Nullable Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HttpResponse response = (HttpResponse) o;
return statusCode == response.statusCode && Objects.equals(headers, response.headers)
&& Objects.equals(content, response.content);

View File

@ -0,0 +1,78 @@
[
{
"id": "CLOUDPLAYER",
"displayName": "My Library",
"description": "Music provider",
"supportedProperties": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedTriggers": [],
"supportedOperations": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedConditions": [],
"availability": "AVAILABLE",
"icon": null,
"providerData": {
"isDefaultMusicProvider": false,
"isDefaultStationProvider": false
}
},
{
"id": "AMAZON_MUSIC",
"displayName": "Amazon Music",
"description": "Music provider",
"supportedProperties": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedTriggers": [],
"supportedOperations": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedConditions": [],
"availability": "AVAILABLE",
"icon": null,
"providerData": {
"isDefaultMusicProvider": true,
"isDefaultStationProvider": true
}
},
{
"id": "TUNEIN",
"displayName": "TuneIn",
"description": "Music provider",
"supportedProperties": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedTriggers": [],
"supportedOperations": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedConditions": [],
"availability": "AVAILABLE",
"icon": null,
"providerData": {
"isDefaultMusicProvider": false,
"isDefaultStationProvider": false
}
},
{
"id": "DEFAULT",
"displayName": "",
"description": "Default Music provider",
"supportedProperties": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedTriggers": [],
"supportedOperations": [
"Alexa.Music.PlaySearchPhrase"
],
"supportedConditions": [],
"availability": "AVAILABLE",
"icon": null,
"providerData": {
"isDefaultMusicProvider": false,
"isDefaultStationProvider": false
}
}
]

View File

@ -0,0 +1,432 @@
{
"data": {
"endpoints": {
"items": [
{
"endpointId": "amzn1.alexa.endpoint.b5bfbc93-0794-42df-a679-cc7861d11e12",
"id": "amzn1.alexa.endpoint.b5bfbc93-0794-42df-a679-cc7861d11e12",
"friendlyName": "Leos Echo Dot",
"displayCategories": {
"primary": {
"value": "ALEXA_VOICE_ENABLED"
}
},
"legacyIdentifiers": {
"dmsIdentifier": {
"deviceType": {
"type": "PLAIN",
"value": {
"text": "A1RABVCI4QCIKC"
}
},
"deviceSerialNumber": {
"type": "PLAIN",
"value": {
"text": "G090XG0901040Q8N"
}
}
}
},
"legacyAppliance": {
"applianceId": "AAA_SonarCloudService_b72d99bf-bf6c-3381-aaff-9a8a40b7eeab",
"applianceTypes": [
"ALEXA_VOICE_ENABLED"
],
"endpointTypeId": "",
"friendlyName": "Leos Echo Dot",
"friendlyDescription": "Amazon smart device",
"manufacturerName": "Amazon",
"connectedVia": "Leos Echo Dot",
"modelName": "",
"entityId": "b5bfbc93-0794-42df-a679-cc7861d11e12",
"actions": [],
"mergedApplianceIds": [
"AlexaBridge_G090XG0901040Q8N@A1RABVCI4QCIKC_G090XG0901040Q8N",
"AAA_SonarCloudService_b72d99bf-bf6c-3381-aaff-9a8a40b7eeab"
],
"capabilities": [
{
"capabilityType": "AVSInterfaceCapability",
"type": "AlexaInterface",
"version": "3",
"properties": {
"supported": [
{
"name": "overallMode"
},
{
"name": "babyCryDetectionState"
},
{
"name": "carbonMonoxideSirenDetectionState"
},
{
"name": "snoreDetectionState"
},
{
"name": "waterSoundsDetectionState"
},
{
"name": "smokeAlarmDetectionState"
},
{
"name": "glassBreakDetectionState"
},
{
"name": "detectionModes"
},
{
"name": "runningWaterDetectionState"
},
{
"name": "coughDetectionState"
},
{
"name": "dogBarkDetectionState"
},
{
"name": "humanPresenceDetectionState"
},
{
"name": "smokeSirenDetectionState"
},
{
"name": "beepingApplianceDetectionState"
}
],
"proactivelyReported": true,
"retrievable": true,
"readOnly": false
},
"configuration": {},
"interfaceName": "Alexa.AcousticEventSensor"
},
{
"type": "AlexaInterface",
"version": "1.0",
"properties": {
"supported": [
{
"name": "proactiveDetection"
}
],
"proactivelyReported": false,
"retrievable": false
},
"capabilityType": "AlexaEndpointCapabilityInstance",
"interfaceName": "Alexa.EndpointDetector"
},
{
"capabilityType": "AVSInterfaceCapability",
"type": "AlexaInterface",
"version": "1.17",
"properties": {
"supported": [
{
"name": "supportedFeatures"
},
{
"name": "LocalEngineState"
}
],
"proactivelyReported": true,
"retrievable": true,
"readOnly": false
},
"interfaceName": "Alexa.LocalExecutionFlow"
},
{
"type": "AlexaInterface",
"version": "1.0",
"configurations": {
"applicationProtocols": [
{
"type": "BLE_MESH",
"maxVersion": "1.0",
"commissioneeTypes": {
"displayCategories": [
"FAN",
"LIGHT",
"OTHER",
"REMOTE",
"SMARTPLUG"
]
}
},
{
"type": "OTHER",
"name": "LAP",
"maxVersion": "1.0",
"commissioneeTypes": {
"displayCategories": [
"LIGHT",
"SMARTPLUG",
"SWITCH"
]
}
},
{
"type": "MATTER",
"maxVersion": "1.3",
"transportProtocols": [
{
"type": "WI-FI",
"maxVersion": "5.0"
}
],
"commissioneeTypes": {
"displayCategories": [
"AIR_CONDITIONER",
"AIR_PURIFIER",
"AIR_QUALITY_MONITOR",
"CARBON_MONOXIDE_SENSOR",
"CONTACT_SENSOR",
"COOLING_CABINET",
"DISHWASHER",
"FAN",
"FREEZER",
"HUB",
"HUMIDITY_SENSOR",
"INTERIOR_BLIND",
"LIGHT",
"LIGHT_SENSOR",
"MOTION_SENSOR",
"OTHER",
"REFRIGERATOR",
"REMOTE",
"SMARTLOCK",
"SMARTPLUG",
"SMOKE_SENSOR",
"SWITCH",
"TEMPERATURE_SENSOR",
"THERMOSTAT",
"VACUUM_CLEANER"
],
"deviceTypes": [
"10",
"14",
"15",
"19",
"21",
"22",
"43",
"44",
"45",
"112",
"113",
"114",
"116",
"117",
"118",
"256",
"257",
"262",
"263",
"266",
"267",
"268",
"269",
"514",
"769",
"770",
"775"
]
}
},
{
"type": "OTHER",
"name": "PHILIPS_BLE",
"maxVersion": "5.0",
"transportProtocols": [
{
"type": "BLE",
"maxVersion": "5.0"
}
],
"commissioneeTypes": {
"displayCategories": [
"LIGHT",
"SMARTPLUG"
]
}
},
{
"type": "PHILIPS_HUE",
"maxVersion": "1.0",
"transportProtocols": [
{
"type": "WI-FI",
"maxVersion": "5.0"
}
],
"commissioneeTypes": {
"displayCategories": [
"LIGHT"
]
}
},
{
"type": "WEMO",
"maxVersion": "1.0",
"transportProtocols": [
{
"type": "WI-FI",
"maxVersion": "5.0"
}
],
"commissioneeTypes": {
"displayCategories": [
"SMARTPLUG",
"SWITCH"
]
}
},
{
"type": "IPP",
"maxVersion": "2.1",
"transportProtocols": [
{
"type": "WI-FI",
"maxVersion": "5.0"
}
],
"commissioneeTypes": {
"displayCategories": [
"PRINTER"
]
}
}
]
},
"capabilityType": "AlexaEndpointCapabilityInstance",
"interfaceName": "Alexa.DeviceDiscovery.Hub"
},
{
"type": "AlexaInterface",
"version": "3.1",
"properties": {
"supported": [
{
"name": "enablement"
},
{
"name": "illuminance"
}
],
"proactivelyReported": true,
"retrievable": true
},
"capabilityType": "AlexaEndpointCapabilityInstance",
"interfaceName": "Alexa.LightSensor"
}
],
"applianceNetworkState": {
"reachability": "REACHABLE",
"lastSeenAt": 1755420019855,
"createdAt": 1755294328906,
"lastSeenDiscoverySessionId": {
"value": "5f458a21-d841-419b-8571-3535cb1232e8"
}
},
"version": "0",
"isEnabled": true,
"customerDefinedDeviceType": "",
"customerPreference": null,
"alexaDeviceIdentifierList": [
{
"dmsDeviceSerialNumber": "G090XG0901040Q8N",
"dmsDeviceTypeId": "A1RABVCI4QCIKC"
}
],
"aliases": [],
"driverIdentity": {
"namespace": "AAA",
"identifier": "SonarCloudService"
},
"additionalApplianceDetails": {
"additionalApplianceDetails": {}
},
"isConsentRequired": false,
"applianceKey": "b5bfbc93-0794-42df-a679-cc7861d11e12",
"appliancePairs": [
"AlexaBridge_G090XG0901040Q8N@A1RABVCI4QCIKC_G090XG0901040Q8N",
"AAA_SonarCloudService_b72d99bf-bf6c-3381-aaff-9a8a40b7eeab"
],
"deduplicatedPairs": [
{
"applianceId": "AAA_SonarCloudService_b72d99bf-bf6c-3381-aaff-9a8a40b7eeab",
"entityId": "b5bfbc93-0794-42df-a679-cc7861d11e12"
},
{
"applianceId": "AlexaBridge_G090XG0901040Q8N@A1RABVCI4QCIKC_G090XG0901040Q8N",
"entityId": "b5bfbc93-0794-42df-a679-cc7861d11e12"
}
],
"entityPairs": [
"b5bfbc93-0794-42df-a679-cc7861d11e12"
],
"deduplicatedAliasesByEntityId": {},
"relations": [
{
"relatedEntityType": "PARENT",
"relationshipEntity": {
"destination": {
"id": "G090XG0901040Q8N",
"entityType": "DMS_ENDPOINT",
"metadata": ""
},
"relationshipType": "IsComponentOf"
}
},
{
"relatedEntityType": "PARENT",
"relationshipEntity": {
"destination": {
"id": "G090XG0901040Q8N",
"entityType": "DMS_ENDPOINT",
"metadata": ""
},
"relationshipType": "IsComponentOf"
}
}
]
},
"serialNumber": {
"value": {
"text": "G090XG0901040Q8N"
}
},
"enablement": "ENABLED",
"model": {
"value": {
"text": "Echo Dot (3rd generation)"
}
},
"manufacturer": {
"value": {
"text": "Amazon"
}
},
"features": [
{
"name": "speaker",
"operations": null
},
{
"name": "connectivity",
"operations": null
},
{
"name": "bluetooth",
"operations": null
}
]
}
]
}
},
"extensions": {
"requestID": "1PPWV08RGFWVFSPWSPH0",
"duration": 58
}
}

View File

@ -13,6 +13,7 @@ The binding does not support auto discovery.
## Thing Configuration
As a minimum, the apiKey is needed:
| Thing Parameter | Default Value | Required | Advanced | Description |
|-----------------|---------------|----------|----------|-------------------------------------------------------------------------------------------------------|
| apiKey | N/A | Yes | No | The API key from the 'Developer' section of <https://apps.amber.com.au> |

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amberelectric</artifactId>

View File

@ -240,7 +240,7 @@ public class AmberElectricHandler extends BaseThingHandler {
&& "controlledLoad".equals(currentPrices.channelType)) {
updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_STATUS,
new StringType(currentPrices.descriptor));
updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_STATUS,
updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_PRICE,
convertPriceToState(currentPrices.perKwh));
}
}
@ -250,7 +250,6 @@ public class AmberElectricHandler extends BaseThingHandler {
sendTimeSeries(AmberElectricBindingConstants.CHANNEL_ELECTRICITY_PRICE, elecTimeSeries);
sendTimeSeries(AmberElectricBindingConstants.CHANNEL_FEED_IN_PRICE, feedInTimeSeries);
}
} catch (AmberElectricCommunicationException e) {
logger.debug("Unexpected error connecting to Amber Electric API", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.ambientweather</artifactId>

View File

@ -12,6 +12,10 @@
*/
package org.openhab.binding.ambientweather.internal.processor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
@ -34,13 +38,14 @@ public class ProcessorFactory {
// Single Gson instance shared by processors
private static final Gson GSON = new Gson();
// Supported weather stations
private @Nullable static Ws1400ipProcessor WS1400IP_PROCESSOR;
private @Nullable static Ws2902aProcessor WS2902A_PROCESSOR;
private @Nullable static Ws2902bProcessor WS2902B_PROCESSOR;
private @Nullable static Ws8482Processor WS8482_PROCESSOR;
private @Nullable static Ws0900ipProcessor WS0900IP_PROCESSOR;
private @Nullable static Ws0265Processor WS0265_PROCESSOR;
// Map of thing types to their processor suppliers
private static final Map<String, Supplier<AbstractProcessor>> PROCESSOR_SUPPLIERS = Map.of(
"ambientweather:ws1400ip", Ws1400ipProcessor::new, "ambientweather:ws2902a", Ws2902aProcessor::new,
"ambientweather:ws2902b", Ws2902bProcessor::new, "ambientweather:ws8482", Ws8482Processor::new,
"ambientweather:ws0900ip", Ws0900ipProcessor::new, "ambientweather:ws0265", Ws0265Processor::new);
// Thread-safe cache for processor instances
private static final Map<String, AbstractProcessor> PROCESSOR_CACHE = new ConcurrentHashMap<>();
/**
* Individual weather station processors use this one Gson instance,
@ -49,69 +54,26 @@ public class ProcessorFactory {
* @return instance of a Gson object
*/
public static Gson getGson() {
return (GSON);
return GSON;
}
/**
* Get a processor for a specific weather station type.
*
* @param thing
* @param thing the Thing for which to get a processor
* @return instance of a weather station processor
* @throws ProcessorNotFoundException
* @throws ProcessorNotFoundException if no processor exists for the thing type
*/
public static AbstractProcessor getProcessor(Thing thing) throws ProcessorNotFoundException {
// Return the processor for this thing type
public static @Nullable AbstractProcessor getProcessor(Thing thing) throws ProcessorNotFoundException {
String thingType = thing.getThingTypeUID().getAsString().toLowerCase();
switch (thingType) {
case "ambientweather:ws1400ip": {
Ws1400ipProcessor processor = WS1400IP_PROCESSOR;
if (processor == null) {
processor = new Ws1400ipProcessor();
WS1400IP_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws2902a": {
Ws2902aProcessor processor = WS2902A_PROCESSOR;
if (processor == null) {
processor = new Ws2902aProcessor();
WS2902A_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws2902b": {
Ws2902bProcessor processor = WS2902B_PROCESSOR;
if (processor == null) {
processor = new Ws2902bProcessor();
WS2902B_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws8482": {
Ws8482Processor processor = WS8482_PROCESSOR;
if (processor == null) {
processor = new Ws8482Processor();
WS8482_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws0900ip": {
Ws0900ipProcessor processor = WS0900IP_PROCESSOR;
if (processor == null) {
processor = new Ws0900ipProcessor();
WS0900IP_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws0265": {
Ws0265Processor processor = WS0265_PROCESSOR;
if (processor == null) {
processor = new Ws0265Processor();
WS0265_PROCESSOR = processor;
}
return processor;
}
// Check if we have a supplier for this thing type
Supplier<AbstractProcessor> supplier = PROCESSOR_SUPPLIERS.get(thingType);
if (supplier == null) {
throw new ProcessorNotFoundException("No processor for thing type " + thingType);
}
throw new ProcessorNotFoundException("No processor for thing type " + thingType);
// Use computeIfAbsent for thread-safe caching
return PROCESSOR_CACHE.computeIfAbsent(thingType, type -> supplier.get());
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amplipi</artifactId>
@ -15,7 +15,7 @@
<name>openHAB Add-ons :: Bundles :: AmpliPi Binding</name>
<properties>
<cxf-version>3.6.5</cxf-version>
<cxf-version>3.6.8</cxf-version>
</properties>
<build>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.androiddebugbridge</artifactId>

View File

@ -163,7 +163,7 @@ KEYPRESS will accept the following commands as strings (case sensitive):
- KEY_SUBMIT
The list above causes an instantanious "press and release" of each button.
If you would like to manually control the press and release of each you may append _PRESS and _RELEASE to the end of each.
If you would like to manually control the press and release of each you may append `_PRESS` and `_RELEASE` to the end of each.
(e.g. KEY_FORWARD_PRESS or KEY_FORWARD_RELEASE)
You may also send an ASCII character as a single letter to simulate a key entry (e.g KEY_A, KEY_1, KEY_z).
@ -225,6 +225,7 @@ This completes the PIN process.
Upon reconnection (either from reconfiguration or a restart of OpenHAB), you should now see a message of "Login Successful" in openhab.log
## Troubleshooting
Some devices come with an outdated version of the "Android TV Remote Service". So in case the PIN Process does not result in a PIN
shown on the screen, and the openHAB log shows an entry
```GoogleTV version on device needs to be updated```
@ -598,4 +599,3 @@ Switch GoogleTV_MUTE "MUTE [%s]" { channel = "androidtv:googletv:theater:mute" }
| 302 | KEYCODE_DEMO_APP_2 |
| 303 | KEYCODE_DEMO_APP_3 |
| 304 | KEYCODE_DEMO_APP_4 |

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.androidtv</artifactId>
@ -16,7 +16,7 @@
<properties>
<bnd.importpackage>!net.sf.ehcache.*,!net.spy.*</bnd.importpackage>
<bouncycastle.version>1.78.1</bouncycastle.version>
<bouncycastle.version>1.81</bouncycastle.version>
</properties>
<dependencies>

View File

@ -221,4 +221,4 @@ end
## Reference Documentation
The UDP protocol of Anel devices is explained [here](https://forum.anel.eu/viewtopic.php?f=16&t=207).
The UDP protocol of Anel devices is explained in the [Anel forum documentation](https://forum.anel.eu/viewtopic.php?f=16&t=207).

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.anel</artifactId>

View File

@ -29,9 +29,9 @@ The Anthem AV processor supports the following channels (some zones/channels are
| Channel | Type | Description |
|-------------------------|---------|--------------|
| *General* | | |
| _General_ | | |
| general#command | String | Send a custom command |
| *Main Zone* | | |
| _Main Zone_ | | |
| 1#power | Switch | Power the zone on or off |
| 1#volume | Dimmer | Increase or decrease the volume level |
| 1#volumeDB | Number | The actual volume setting |
@ -39,7 +39,7 @@ The Anthem AV processor supports the following channels (some zones/channels are
| 1#activeInput | Number | The currently active input source |
| 1#activeInputShortName | String | Short friendly name of the active input |
| 1#activeInputLongName | String | Long friendly name of the active input |
| *Zone 2* | | |
| _Zone 2_ | | |
| 2#power | Switch | Power the zone on or off |
| 2#volume | Dimmer | Increase or decrease the volume level |
| 2#volumeDB | Number | The actual volume setting |

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.anthem</artifactId>

View File

@ -21,6 +21,10 @@
</channel-group>
</channel-groups>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description>
<parameter name="host" type="text" required="true">
<label>Network Address</label>

View File

@ -1,14 +1,14 @@
# ArgoClima Binding
The binding provides support for [ArgoClima](https://argoclima.com/en/) Wi-Fi-enabled air conditioning devices which use ***Argo Web APP*** for control.
The binding provides support for [ArgoClima](https://argoclima.com/en/) Wi-Fi-enabled air conditioning devices which use _Argo Web APP_ for control.
Refer to [Argo Web APP details](#argo-web-app-details) section for an example.
> ***IMPORTANT:*** The same vendor also manufactures HVAC devices supported by a [phone application](https://www.youtube.com/playlist?list=PLQiJByZqkxY-4IjmviF2U-Grg_qYTzpKn).
> **IMPORTANT:** The same vendor also manufactures HVAC devices supported by a [phone application](https://www.youtube.com/playlist?list=PLQiJByZqkxY-4IjmviF2U-Grg_qYTzpKn).
>
> These devices are using a different protocol and are ***not*** supported by this binding.
> These devices are using a different protocol and are _not_ supported by this binding.
> There are good chances these will be supported by the [Gree](https://www.openhab.org/addons/bindings/gree/) binding, though!
The binding supports all HVAC remote functions (including built-in schedule and settings) except for ***iFeel*** (room) temperature which is not supported by the Argo remote protocol and has to be sent via infrared.
The binding supports all HVAC remote functions (including built-in schedule and settings) except for _iFeel_ (room) temperature which is not supported by the Argo remote protocol and has to be sent via infrared.
The binding can operate in local, remote and hybrid modes.
Refer to [Connection Modes](#connection-modes) for more details.
@ -17,7 +17,7 @@ See also [Argo protocol details](#argo-protocol-details) to find out more about
## Supported Things
- `remote`: Represents a HVAC device which is controlled remotely - through vendor's web application
- `local`: Represents a locally available device, which openHAB interacts with directly *(or indirectly, through a stub server)*. Refer to [Connection Modes](#connection-modes) for more details.
- `local`: Represents a locally available device, which openHAB interacts with directly _(or indirectly, through a stub server)_. Refer to [Connection Modes](#connection-modes) for more details.
The binding has been primarily developed and tested using [Ulisse 13 DCI ECO Wi-Fi](https://argoclima.com/en/prodotti/argo-ulisse-eco/) device.
@ -25,7 +25,7 @@ The binding has been primarily developed and tested using [Ulisse 13 DCI ECO Wi-
The binding does not support device auto-discovery (as the devices don't announce themselves locally).
- Note it is *technically* possible for the advanced mode with API stub to discover devices, but as it requires manual firewall reconfiguration, it won't be an "auto" anyway so was not implemented.
- Note it is _technically_ possible for the advanced mode with API stub to discover devices, but as it requires manual firewall reconfiguration, it won't be an "auto" anyway so was not implemented.
## Thing Configuration
@ -65,13 +65,13 @@ The same values apply to **both** `remote` and `local`.
| Name | Type | Description | Default | Required | Advanced |
|------------------------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|----------|----------|
| schedule1DayOfWeek | text(multiple) | Days (set comprising of values ```MON```, ```TUE```, ```WED```, ```THU```, ```FRI```, ```SAT```, ``SUN``), when Schedule Timer 1 actions should be performed. This is used only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_1``` mode. | [MON, TUE, WED, THU, FRI, SAT, SUN] | no | yes |
| schedule1OnTime | text | The time of day (HH:MM) the device should turn **ON** *(in the last used mode)* on the ```schedule1DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_1``` mode | 8:00 | no | yes |
| schedule1OnTime | text | The time of day (HH:MM) the device should turn **ON** _(in the last used mode)_ on the `schedule1DayOfWeek`-specified days. In effect only if `active-timer` [channel](#channels) is in `SCHEDULE_TIMER_1` mode | 8:00 | no | yes |
| schedule1OffTime | text | The time of day (HH:MM) the device should turn **OFF** on the ```schedule1DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_1``` mode | 18:00 | no | yes |
| schedule2DayOfWeek | text(multiple) | Days (set comprising of values ```MON```, ```TUE```, ```WED```, ```THU```, ```FRI```, ```SAT```, ``SUN``), when Schedule Timer 1 actions should be performed. This is used only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_1``` mode. | [MON, TUE, WED, THU, FRI] | no | yes |
| schedule2OnTime | text | The time of day (HH:MM) the device should turn **ON** *(in the last used mode)* on the ```schedule2DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_2``` mode | 15:00 | no | yes |
| schedule2OnTime | text | The time of day (HH:MM) the device should turn **ON** _(in the last used mode)_ on the ```schedule2DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_2``` mode | 15:00 | no | yes |
| schedule2OffTime | text | The time of day (HH:MM) the device should turn **OFF** on the ```schedule2DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_2``` mode | 20:00 | no | yes |
| schedule3DayOfWeek | text(multiple) | Days (set comprising of values ```MON```, ```TUE```, ```WED```, ```THU```, ```FRI```, ```SAT```, ``SUN``), when Schedule Timer 1 actions should be performed. This is used only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_1``` mode. | [SAT, SUN] | no | yes |
| schedule3OnTime | text | The time of day (HH:MM) the device should turn **ON** *(in the last used mode)* on the ```schedule3DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_3``` mode | 11:00 | no | yes |
| schedule3OnTime | text | The time of day (HH:MM) the device should turn **ON** _(in the last used mode)_ on the ```schedule3DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_3``` mode | 11:00 | no | yes |
| schedule3OffTime | text | The time of day (HH:MM) the device should turn **OFF** on the ```schedule3DayOfWeek```-specified days. In effect only if ```active-timer``` [channel](#channels) is in ```SCHEDULE_TIMER_3``` mode | 22:00 | no | yes |
| resetToFactoryDefaults | boolean(action) | When set, upon successful Thing initialization, the binding will issue a one-time factory reset request to the device (and flip this value back do OFF) | false | no | yes |
@ -89,8 +89,8 @@ Both thing types are functionally equivalent and support the same channels.
| fan-speed | String | RW | Fan mode. One of: ```AUTO```, ```LEVEL_1```, ```LEVEL_2```, ```LEVEL_3```, ```LEVEL_4```, ```LEVEL_5```, ```LEVEL_6``` |
| - **Operation Modes** (#modes) ||||
| eco-mode | Switch | RW | Economy (Energy Saving) Mode (cap device max power to the ```eco-power-limit```) |
| turbo-mode | Switch | RW | Turbo mode (max power). *While the device API (similarly to original remote) allows enabling ```turbo``` **while** ```night``` and/or ```economy``` modes are **active**, actual effect of such a combo is unknown :)* |
| night-mode | Switch | RW | Night mode *(lowers device noise by lowering the fan speed and automatically raising the set temperature by 1°C after 60 minutes of enabling this option)* |
| turbo-mode | Switch | RW | Turbo mode (max power). _While the device API (similarly to original remote) allows enabling ```turbo``` **while** ```night``` and/or ```economy``` modes are **active**, actual effect of such a combo is unknown :)_ |
| night-mode | Switch | RW | Night mode _(lowers device noise by lowering the fan speed and automatically raising the set temperature by 1°C after 60 minutes of enabling this option)_ |
| - **Timers (advanced)** (#timers) ||||
| active-timer | String | RW | Active timer. One of ```NO_TIMER```, ```DELAY_TIMER```, ```SCHEDULE_TIMER_1```, ```SCHEDULE_TIMER_2```, ```SCHEDULE_TIMER_3```. See also [schedule configuration](#general-device-configuration-dynamic) |
| delay-timer | Number:Time | W | Delay timer value. In effect only if ```active-timer``` is in ```DELAY_TIMER``` mode. The delay timer toggles the current ```power``` (ex. OFF->ON) after the configured period elapses |
@ -347,7 +347,7 @@ Please note this forwarding rule would need to be accompanied with other traffic
In this mode openHAB is acting as an **almost** transparent proxy, and does a pass-through of device-side messages and remote-side responses (a man-in-the-middle).
This allows to have the device fully controllable via openHAB **as well as** vendor's application (at the expense of security!).
Possible other use of this mode is for firmware update or ad-hoc controlling some settings which are not easily accessible via openHAB.
> ***IMPORTANT***: Most of the time, openHAB serves as a fully transparent proxy, not interfering with the traffic, **except for** cases when cloud has no updates for the device while openHAB **has** a command pending send to the device.
> **IMPORTANT:** Most of the time, openHAB serves as a fully transparent proxy, not interfering with the traffic, _except for_ cases when cloud has no updates for the device while openHAB _has_ a command pending send to the device.
> In such case, the binding injects it into the communication flow as-if it was cloud-issued!
![Advanced local connection diagram: REMOTE_API_PROXY mode](doc/Argoclima_connection_Advanced_REMOTE_API_PROXY.png)
@ -366,14 +366,14 @@ The HVAC device accepts multiple command in one request (similarly to how the re
Dual APIs (local and remote) are exposed:
- The **local** API uses direct HTTP communication (all requests are ```HTTP GET```) and polling for getting the device state.
Sending any command through this interface effects an *immediate* change, and audible confirmation (beep).
Sending any command through this interface effects an _immediate_ change, and audible confirmation (beep).
- The **remote** API involves the device periodically (for example, every minute) reaching out to manufacturer's server, and getting any withstanding commands.
Commands sent through this interface will be *delayed*, and not yield an audible confirmation (no beep).
Commands sent through this interface will be _delayed_, and not yield an audible confirmation (no beep).
**IMPORTANT**: The Argo HVAC device ***has to*** be connected to Wi-Fi and communicating with a vendor (or vendor-like) server for either of its APIs to work.
**IMPORTANT:** The Argo HVAC device has to be connected to Wi-Fi and communicating with a vendor (or vendor-like) server for either of its APIs to work.
This is true even if the device is desired to be controlled via local APIs only!
> ***A NOTE ON SECURITY:*** The device protocol is plain HTTP (no TLS), and it transmits all the device secrets to the cloud service in cleartext (that includes device password as well as **your Wi-Fi password**!)
> **A NOTE ON SECURITY:** The device protocol is plain HTTP (no TLS), and it transmits all the device secrets to the cloud service in cleartext (that includes device password as well as your Wi-Fi password!)
>
> Hence, security-savvy users may choose to not only connect it to a dedicated ```IOT```-specific Wi-Fi network, but also deny its Internet access (ex. to prevent a malicious firmware update converting it to a network backdoor).
> While the device needs to communicate with **a** protocol-compatible server to work, this binding provides a convenient simulated server exactly for this purpose!

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.argoclima</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.astro</artifactId>

View File

@ -14,8 +14,6 @@ package org.openhab.binding.astro.internal;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -23,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.handler.MoonHandler;
import org.openhab.binding.astro.internal.handler.SunHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
@ -44,15 +43,16 @@ import org.osgi.service.component.annotations.Reference;
public class AstroHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_SUN, THING_TYPE_MOON);
private static final Map<String, AstroThingHandler> ASTRO_THING_HANDLERS = new HashMap<>();
private final CronScheduler scheduler;
private final TimeZoneProvider timeZoneProvider;
private final LocaleProvider localeProvider;
@Activate
public AstroHandlerFactory(final @Reference CronScheduler scheduler,
final @Reference TimeZoneProvider timeZoneProvider) {
final @Reference TimeZoneProvider timeZoneProvider, @Reference LocaleProvider localeProvider) {
this.scheduler = scheduler;
this.timeZoneProvider = timeZoneProvider;
this.localeProvider = localeProvider;
}
@Override
@ -65,23 +65,10 @@ public class AstroHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
AstroThingHandler thingHandler = null;
if (thingTypeUID.equals(THING_TYPE_SUN)) {
thingHandler = new SunHandler(thing, scheduler, timeZoneProvider);
thingHandler = new SunHandler(thing, scheduler, timeZoneProvider, localeProvider);
} else if (thingTypeUID.equals(THING_TYPE_MOON)) {
thingHandler = new MoonHandler(thing, scheduler, timeZoneProvider);
}
if (thingHandler != null) {
ASTRO_THING_HANDLERS.put(thing.getUID().toString(), thingHandler);
thingHandler = new MoonHandler(thing, scheduler, timeZoneProvider, localeProvider);
}
return thingHandler;
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ASTRO_THING_HANDLERS.remove(thing.getUID().toString());
}
public static @Nullable AstroThingHandler getHandler(String thingUid) {
return ASTRO_THING_HANDLERS.get(thingUid);
}
}

View File

@ -99,7 +99,7 @@ public class AstroActions implements ThingActions {
if (theHandler != null) {
if (theHandler instanceof SunHandler sunHandler) {
Radiation radiation = sunHandler.getRadiationAt(date != null ? date : ZonedDateTime.now());
return radiation.getTotal();
return radiation == null ? null : radiation.getTotal();
} else {
logger.info("Astro Action service ThingHandler is not a SunHandler!");
}

View File

@ -15,7 +15,10 @@ package org.openhab.binding.astro.internal.calc;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.EclipseKind;
import org.openhab.binding.astro.internal.model.EclipseType;
@ -39,6 +42,7 @@ import org.openhab.binding.astro.internal.util.DateTimeUtils;
* http://www.computus.de/mondphase/mondphase.htm azimuth/elevation and
* zodiac based on http://lexikon.astronomie.info/java/sunmoon/
*/
@NonNullByDefault
public class MoonCalc {
private static final double NEW_MOON = 0;
private static final double FULL_MOON = 0.5;
@ -48,7 +52,7 @@ public class MoonCalc {
/**
* Calculates all moon data at the specified coordinates
*/
public Moon getMoonInfo(Calendar calendar, double latitude, double longitude) {
public Moon getMoonInfo(Calendar calendar, double latitude, double longitude, TimeZone zone, Locale locale) {
Moon moon = new Moon();
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
@ -75,26 +79,31 @@ public class MoonCalc {
moon.setSet(new Range(set, set));
MoonPhase phase = moon.getPhase();
phase.setNew(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, NEW_MOON)));
phase.setFirstQuarter(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FIRST_QUARTER)));
phase.setFull(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FULL_MOON)));
phase.setThirdQuarter(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, LAST_QUARTER)));
phase.setNew(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, NEW_MOON), zone, locale));
phase.setFirstQuarter(
DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FIRST_QUARTER), zone, locale));
phase.setFull(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FULL_MOON), zone, locale));
phase.setThirdQuarter(
DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, LAST_QUARTER), zone, locale));
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
double jdate = getEclipse(calendar, EclipseType.MOON, julianDateMidnight, eclipseKind);
eclipse.set(eclipseKind, DateTimeUtils.toCalendar(jdate), new Position());
Calendar eclipseDate = DateTimeUtils.toCalendar(jdate, zone, locale);
if (eclipseDate != null) {
eclipse.set(eclipseKind, eclipseDate, new Position());
}
});
double decimalYear = DateTimeUtils.getDecimalYear(calendar);
MoonDistance apogee = moon.getApogee();
double apogeeJd = getApogee(julianDate, decimalYear);
apogee.setDate(DateTimeUtils.toCalendar(apogeeJd));
apogee.setDate(DateTimeUtils.toCalendar(apogeeJd, zone, locale));
apogee.setDistance(getDistance(apogeeJd));
MoonDistance perigee = moon.getPerigee();
double perigeeJd = getPerigee(julianDate, decimalYear);
perigee.setDate(DateTimeUtils.toCalendar(perigeeJd));
perigee.setDate(DateTimeUtils.toCalendar(perigeeJd, zone, locale));
perigee.setDistance(getDistance(perigeeJd));
return moon;
@ -103,28 +112,37 @@ public class MoonCalc {
/**
* Calculates the moon illumination and distance.
*/
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, Moon moon) {
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, Moon moon, TimeZone zone,
Locale locale) {
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
setMoonPhase(calendar, moon);
setMoonPhase(calendar, moon, zone, locale);
setAzimuthElevationZodiac(julianDate, latitude, longitude, moon);
MoonDistance distance = moon.getDistance();
distance.setDate(Calendar.getInstance());
distance.setDate(calendar);
distance.setDistance(getDistance(julianDate));
}
/**
* Calculates the age and the current phase.
*/
private void setMoonPhase(Calendar calendar, Moon moon) {
private void setMoonPhase(Calendar calendar, Moon moon, TimeZone zone, Locale locale) {
MoonPhase phase = moon.getPhase();
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
double parentNewMoon = getPreviousPhase(calendar, julianDate, NEW_MOON);
double age = Math.abs(parentNewMoon - julianDate);
Calendar parentNewMoonCal = DateTimeUtils.toCalendar(parentNewMoon, zone, locale);
if (parentNewMoonCal == null) {
return;
}
phase.setAge(age);
long parentNewMoonMillis = DateTimeUtils.toCalendar(parentNewMoon).getTimeInMillis();
long ageRangeTimeMillis = phase.getNew().getTimeInMillis() - parentNewMoonMillis;
long parentNewMoonMillis = parentNewMoonCal.getTimeInMillis();
Calendar cal = phase.getNew();
if (cal == null) {
return;
}
long ageRangeTimeMillis = cal.getTimeInMillis() - parentNewMoonMillis;
long ageCurrentMillis = System.currentTimeMillis() - parentNewMoonMillis;
double agePercent = ageRangeTimeMillis != 0 ? ageCurrentMillis * 100.0 / ageRangeTimeMillis : 0;
phase.setAgePercent(agePercent);

View File

@ -13,7 +13,11 @@
package org.openhab.binding.astro.internal.calc;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.model.Season;
import org.openhab.binding.astro.internal.model.SeasonName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
@ -24,44 +28,61 @@ import org.openhab.binding.astro.internal.util.DateTimeUtils;
* @author Gerhard Riegler - Initial contribution
* @implNote based on the calculations of http://stellafane.org/misc/equinox.html
*/
@NonNullByDefault
public class SeasonCalc {
private int currentYear;
private Season currentSeason;
private @Nullable Season currentSeason;
/**
* Returns the seasons of the year of the specified calendar.
*/
public Season getSeason(Calendar calendar, double latitude, boolean useMeteorologicalSeason) {
public Season getSeason(Calendar calendar, double latitude, boolean useMeteorologicalSeason, TimeZone zone,
Locale locale) {
int year = calendar.get(Calendar.YEAR);
boolean isSouthernHemisphere = latitude < 0.0;
Season season = currentSeason;
if (currentYear != year) {
season = new Season();
if (!isSouthernHemisphere) {
season.setSpring(calcEquiSol(0, year));
season.setSummer(calcEquiSol(1, year));
season.setAutumn(calcEquiSol(2, year));
season.setWinter(calcEquiSol(3, year));
season.setSpring(calcEquiSol(0, year, zone, locale));
season.setSummer(calcEquiSol(1, year, zone, locale));
season.setAutumn(calcEquiSol(2, year, zone, locale));
season.setWinter(calcEquiSol(3, year, zone, locale));
} else {
season.setSpring(calcEquiSol(2, year));
season.setSummer(calcEquiSol(3, year));
season.setAutumn(calcEquiSol(0, year));
season.setWinter(calcEquiSol(1, year));
season.setSpring(calcEquiSol(2, year, zone, locale));
season.setSummer(calcEquiSol(3, year, zone, locale));
season.setAutumn(calcEquiSol(0, year, zone, locale));
season.setWinter(calcEquiSol(1, year, zone, locale));
}
currentSeason = season;
currentYear = year;
}
if (useMeteorologicalSeason) {
atMidnightOfFirstMonthDay(season.getSpring());
atMidnightOfFirstMonthDay(season.getSummer());
atMidnightOfFirstMonthDay(season.getAutumn());
atMidnightOfFirstMonthDay(season.getWinter());
if (useMeteorologicalSeason && season != null) {
Calendar cal = season.getSpring();
if (cal != null) {
atMidnightOfFirstMonthDay(cal);
}
cal = season.getSummer();
if (cal != null) {
atMidnightOfFirstMonthDay(cal);
}
cal = season.getAutumn();
if (cal != null) {
atMidnightOfFirstMonthDay(cal);
}
cal = season.getWinter();
if (cal != null) {
atMidnightOfFirstMonthDay(cal);
}
}
season.setName(!isSouthernHemisphere ? getCurrentSeasonNameNorthern(calendar)
: getCurrentSeasonNameSouthern(calendar));
return season;
if (season != null) {
season.setName(!isSouthernHemisphere ? getCurrentSeasonNameNorthern(calendar)
: getCurrentSeasonNameSouthern(calendar));
return season;
}
return new Season();
}
private void atMidnightOfFirstMonthDay(Calendar calendar) {
@ -75,19 +96,28 @@ public class SeasonCalc {
/**
* Returns the current season name for the northern hemisphere.
*/
@Nullable
private SeasonName getCurrentSeasonNameNorthern(Calendar calendar) {
Season currentSeason = this.currentSeason;
if (currentSeason == null) {
return null;
}
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getSpring().getTimeInMillis()
|| currentMillis >= currentSeason.getWinter().getTimeInMillis()) {
Calendar spring = currentSeason.getSpring();
Calendar summer = currentSeason.getSummer();
Calendar autumn = currentSeason.getAutumn();
Calendar winter = currentSeason.getWinter();
if ((spring != null && currentMillis < spring.getTimeInMillis())
|| (winter != null && currentMillis >= winter.getTimeInMillis())) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
} else if (spring != null && summer != null && currentMillis >= spring.getTimeInMillis()
&& currentMillis < summer.getTimeInMillis()) {
return SeasonName.SPRING;
} else if (currentMillis >= currentSeason.getSummer().getTimeInMillis()
&& currentMillis < currentSeason.getAutumn().getTimeInMillis()) {
} else if (summer != null && autumn != null && currentMillis >= summer.getTimeInMillis()
&& currentMillis < autumn.getTimeInMillis()) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
} else if (autumn != null && winter != null && currentMillis >= autumn.getTimeInMillis()
&& currentMillis < winter.getTimeInMillis()) {
return SeasonName.AUTUMN;
}
return null;
@ -96,19 +126,28 @@ public class SeasonCalc {
/**
* Returns the current season name for the southern hemisphere.
*/
@Nullable
private SeasonName getCurrentSeasonNameSouthern(Calendar calendar) {
Season currentSeason = this.currentSeason;
if (currentSeason == null) {
return null;
}
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getAutumn().getTimeInMillis()
|| currentMillis >= currentSeason.getSummer().getTimeInMillis()) {
Calendar spring = currentSeason.getSpring();
Calendar summer = currentSeason.getSummer();
Calendar autumn = currentSeason.getAutumn();
Calendar winter = currentSeason.getWinter();
if ((autumn != null && currentMillis < autumn.getTimeInMillis())
|| (summer != null && currentMillis >= summer.getTimeInMillis())) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
} else if (autumn != null && winter != null && currentMillis >= autumn.getTimeInMillis()
&& currentMillis < winter.getTimeInMillis()) {
return SeasonName.AUTUMN;
} else if (currentMillis >= currentSeason.getWinter().getTimeInMillis()
&& currentMillis < currentSeason.getSpring().getTimeInMillis()) {
} else if (winter != null && spring != null && currentMillis >= winter.getTimeInMillis()
&& currentMillis < spring.getTimeInMillis()) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
} else if (spring != null && summer != null && currentMillis >= spring.getTimeInMillis()
&& currentMillis < summer.getTimeInMillis()) {
return SeasonName.SPRING;
}
return null;
@ -117,14 +156,15 @@ public class SeasonCalc {
/**
* Calculates the date of the season.
*/
private Calendar calcEquiSol(int season, int year) {
@Nullable
private Calendar calcEquiSol(int season, int year, TimeZone zone, Locale locale) {
double estimate = calcInitial(season, year);
double t = (estimate - 2451545.0) / 36525;
double w = 35999.373 * t - 2.47;
double dl = 1 + 0.0334 * cosDeg(w) + 0.0007 * cosDeg(2 * w);
double s = periodic24(t);
double julianDate = estimate + ((0.00001 * s) / dl);
return DateTimeUtils.toCalendar(julianDate);
return DateTimeUtils.toCalendar(julianDate, zone, locale);
}
/**

View File

@ -18,9 +18,13 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.EclipseType;
import org.openhab.binding.astro.internal.model.Position;
@ -37,6 +41,7 @@ import org.openhab.binding.astro.internal.util.DateTimeUtils;
* @author Christoph Weitkamp - Introduced UoM
* @implNote based on the calculations of http://www.suncalc.net
*/
@NonNullByDefault
public class SunCalc {
private static final double J2000 = 2451545.0;
private static final double SC = 1367; // Solar constant in W/m²
@ -69,7 +74,8 @@ public class SunCalc {
/**
* Calculates the sun position (azimuth and elevation).
*/
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, Double altitude, Sun sun) {
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, @Nullable Double altitude,
Sun sun) {
double lw = -longitude * DEG2RAD;
double phi = latitude * DEG2RAD;
@ -96,7 +102,7 @@ public class SunCalc {
/**
* Calculates sun radiation data.
*/
private void setRadiationInfo(Calendar calendar, double elevation, Double altitude, Sun sun) {
private void setRadiationInfo(Calendar calendar, double elevation, @Nullable Double altitude, Sun sun) {
double sinAlpha = Math.sin(DEG2RAD * elevation);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
@ -125,7 +131,7 @@ public class SunCalc {
/**
* Returns true, if the sun is up all day (no rise and set).
*/
private boolean isSunUpAllDay(Calendar calendar, double latitude, double longitude, Double altitude) {
private boolean isSunUpAllDay(Calendar calendar, double latitude, double longitude, @Nullable Double altitude) {
Calendar cal = DateTimeUtils.truncateToMidnight(calendar);
Sun sun = new Sun();
for (int minutes = 0; minutes <= MINUTES_PER_DAY; minutes += CURVE_TIME_INTERVAL) {
@ -141,13 +147,13 @@ public class SunCalc {
/**
* Calculates all sun rise and sets at the specified coordinates.
*/
public Sun getSunInfo(Calendar calendar, double latitude, double longitude, Double altitude,
boolean useMeteorologicalSeason) {
return getSunInfo(calendar, latitude, longitude, altitude, false, useMeteorologicalSeason);
public Sun getSunInfo(Calendar calendar, double latitude, double longitude, @Nullable Double altitude,
boolean useMeteorologicalSeason, TimeZone zone, Locale locale) {
return getSunInfo(calendar, latitude, longitude, altitude, false, useMeteorologicalSeason, zone, locale);
}
private Sun getSunInfo(Calendar calendar, double latitude, double longitude, Double altitude, boolean onlyAstro,
boolean useMeteorologicalSeason) {
private Sun getSunInfo(Calendar calendar, double latitude, double longitude, @Nullable Double altitude,
boolean onlyAstro, boolean useMeteorologicalSeason, TimeZone zone, Locale locale) {
double lw = -longitude * DEG2RAD;
double phi = latitude * DEG2RAD;
double j = DateTimeUtils.midnightDateToJulianDate(calendar) + 0.5;
@ -176,23 +182,31 @@ public class SunCalc {
double jastro2 = getSunriseJulianDate(jtransit, jdark);
Sun sun = new Sun();
sun.setAstroDawn(new Range(DateTimeUtils.toCalendar(jastro2), DateTimeUtils.toCalendar(jnau2)));
sun.setAstroDusk(new Range(DateTimeUtils.toCalendar(jastro), DateTimeUtils.toCalendar(jdark)));
sun.setAstroDawn(new Range(DateTimeUtils.toCalendar(jastro2, zone, locale),
DateTimeUtils.toCalendar(jnau2, zone, locale)));
sun.setAstroDusk(new Range(DateTimeUtils.toCalendar(jastro, zone, locale),
DateTimeUtils.toCalendar(jdark, zone, locale)));
if (onlyAstro) {
return sun;
}
sun.setNoon(new Range(DateTimeUtils.toCalendar(jtransit),
DateTimeUtils.toCalendar(jtransit + JD_ONE_MINUTE_FRACTION)));
sun.setRise(new Range(DateTimeUtils.toCalendar(jrise), DateTimeUtils.toCalendar(jriseend)));
sun.setSet(new Range(DateTimeUtils.toCalendar(jsetstart), DateTimeUtils.toCalendar(jset)));
sun.setNoon(new Range(DateTimeUtils.toCalendar(jtransit, zone, locale),
DateTimeUtils.toCalendar(jtransit + JD_ONE_MINUTE_FRACTION, zone, locale)));
sun.setRise(new Range(DateTimeUtils.toCalendar(jrise, zone, locale),
DateTimeUtils.toCalendar(jriseend, zone, locale)));
sun.setSet(new Range(DateTimeUtils.toCalendar(jsetstart, zone, locale),
DateTimeUtils.toCalendar(jset, zone, locale)));
sun.setCivilDawn(new Range(DateTimeUtils.toCalendar(jciv2), DateTimeUtils.toCalendar(jrise)));
sun.setCivilDusk(new Range(DateTimeUtils.toCalendar(jset), DateTimeUtils.toCalendar(jnau)));
sun.setCivilDawn(new Range(DateTimeUtils.toCalendar(jciv2, zone, locale),
DateTimeUtils.toCalendar(jrise, zone, locale)));
sun.setCivilDusk(
new Range(DateTimeUtils.toCalendar(jset, zone, locale), DateTimeUtils.toCalendar(jnau, zone, locale)));
sun.setNauticDawn(new Range(DateTimeUtils.toCalendar(jnau2), DateTimeUtils.toCalendar(jciv2)));
sun.setNauticDusk(new Range(DateTimeUtils.toCalendar(jnau), DateTimeUtils.toCalendar(jastro)));
sun.setNauticDawn(new Range(DateTimeUtils.toCalendar(jnau2, zone, locale),
DateTimeUtils.toCalendar(jciv2, zone, locale)));
sun.setNauticDusk(new Range(DateTimeUtils.toCalendar(jnau, zone, locale),
DateTimeUtils.toCalendar(jastro, zone, locale)));
boolean isSunUpAllDay = isSunUpAllDay(calendar, latitude, longitude, altitude);
@ -210,23 +224,26 @@ public class SunCalc {
// morning night
Sun sunYesterday = getSunInfo(addDays(calendar, -1), latitude, longitude, altitude, true,
useMeteorologicalSeason);
useMeteorologicalSeason, zone, locale);
Range morningNightRange = null;
if (sunYesterday.getAstroDusk().getEnd() != null
&& DateTimeUtils.isSameDay(sunYesterday.getAstroDusk().getEnd(), calendar)) {
morningNightRange = new Range(sunYesterday.getAstroDusk().getEnd(), sun.getAstroDawn().getStart());
} else if (isSunUpAllDay || sun.getAstroDawn().getStart() == null) {
Range range, range2;
if ((range = sunYesterday.getAstroDusk()) != null && range.getEnd() != null
&& DateTimeUtils.isSameDay(range.getEnd(), calendar)) {
morningNightRange = new Range(range.getEnd(),
(range2 = sun.getAstroDawn()) == null ? null : range2.getStart());
} else if (isSunUpAllDay || (range2 = sun.getAstroDawn()) == null || range2.getStart() == null) {
morningNightRange = new Range();
} else {
morningNightRange = new Range(DateTimeUtils.truncateToMidnight(calendar), sun.getAstroDawn().getStart());
morningNightRange = new Range(DateTimeUtils.truncateToMidnight(calendar),
(range2 = sun.getAstroDawn()) == null ? null : range2.getStart());
}
sun.setMorningNight(morningNightRange);
// evening night
Range eveningNightRange = null;
if (sun.getAstroDusk().getEnd() != null && DateTimeUtils.isSameDay(sun.getAstroDusk().getEnd(), calendar)) {
eveningNightRange = new Range(sun.getAstroDusk().getEnd(),
DateTimeUtils.truncateToMidnight(addDays(calendar, 1)));
if ((range = sun.getAstroDusk()) != null && range.getEnd() != null
&& DateTimeUtils.isSameDay(range.getEnd(), calendar)) {
eveningNightRange = new Range(range.getEnd(), DateTimeUtils.truncateToMidnight(addDays(calendar, 1)));
} else {
eveningNightRange = new Range();
}
@ -237,8 +254,9 @@ public class SunCalc {
sun.setNight(new Range());
} else {
Sun sunTomorrow = getSunInfo(addDays(calendar, 1), latitude, longitude, altitude, true,
useMeteorologicalSeason);
sun.setNight(new Range(sun.getAstroDusk().getEnd(), sunTomorrow.getAstroDawn().getStart()));
useMeteorologicalSeason, zone, locale);
sun.setNight(new Range((range = sun.getAstroDusk()) == null ? null : range.getEnd(),
(range2 = sunTomorrow.getAstroDawn()) == null ? null : range2.getStart()));
}
// eclipse
@ -247,14 +265,17 @@ public class SunCalc {
eclipse.getKinds().forEach(eclipseKind -> {
double jdate = mc.getEclipse(calendar, EclipseType.SUN, j, eclipseKind);
eclipse.set(eclipseKind, DateTimeUtils.toCalendar(jdate), new Position());
Calendar eclipseDate = DateTimeUtils.toCalendar(jdate, zone, locale);
if (eclipseDate != null) {
eclipse.set(eclipseKind, eclipseDate, new Position());
}
});
SunZodiacCalc zodiacCalc = new SunZodiacCalc();
SunZodiacCalc zodiacCalc = new SunZodiacCalc(zone, locale);
zodiacCalc.getZodiac(calendar).ifPresent(z -> sun.setZodiac(z));
SeasonCalc seasonCalc = new SeasonCalc();
sun.setSeason(seasonCalc.getSeason(calendar, latitude, useMeteorologicalSeason));
sun.setSeason(seasonCalc.getSeason(calendar, latitude, useMeteorologicalSeason, zone, locale));
// phase
for (Entry<SunPhaseName, Range> rangeEntry : sortByValue(sun.getAllRanges()).entrySet()) {

View File

@ -16,8 +16,10 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.model.SunZodiac;
@ -33,6 +35,14 @@ import org.openhab.binding.astro.internal.util.DateTimeUtils;
public class SunZodiacCalc {
private Map<Integer, List<SunZodiac>> zodiacsByYear = new HashMap<>();
private final TimeZone zone;
private final Locale locale;
public SunZodiacCalc(TimeZone zone, Locale locale) {
this.zone = zone;
this.locale = locale;
}
/**
* Returns the zodiac for the specified calendar.
*/
@ -58,31 +68,31 @@ public class SunZodiacCalc {
List<SunZodiac> zodiacs = new ArrayList<>();
zodiacs.add(new SunZodiac(ZodiacSign.ARIES,
DateTimeUtils.getRange(year, Calendar.MARCH, 21, year, Calendar.APRIL, 19)));
DateTimeUtils.getRange(year, Calendar.MARCH, 21, year, Calendar.APRIL, 19, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.TAURUS,
DateTimeUtils.getRange(year, Calendar.APRIL, 20, year, Calendar.MAY, 20)));
DateTimeUtils.getRange(year, Calendar.APRIL, 20, year, Calendar.MAY, 20, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.GEMINI,
DateTimeUtils.getRange(year, Calendar.MAY, 21, year, Calendar.JUNE, 20)));
DateTimeUtils.getRange(year, Calendar.MAY, 21, year, Calendar.JUNE, 20, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.CANCER,
DateTimeUtils.getRange(year, Calendar.JUNE, 21, year, Calendar.JULY, 22)));
DateTimeUtils.getRange(year, Calendar.JUNE, 21, year, Calendar.JULY, 22, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.LEO,
DateTimeUtils.getRange(year, Calendar.JULY, 23, year, Calendar.AUGUST, 22)));
DateTimeUtils.getRange(year, Calendar.JULY, 23, year, Calendar.AUGUST, 22, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.VIRGO,
DateTimeUtils.getRange(year, Calendar.AUGUST, 23, year, Calendar.SEPTEMBER, 22)));
DateTimeUtils.getRange(year, Calendar.AUGUST, 23, year, Calendar.SEPTEMBER, 22, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.LIBRA,
DateTimeUtils.getRange(year, Calendar.SEPTEMBER, 23, year, Calendar.OCTOBER, 22)));
DateTimeUtils.getRange(year, Calendar.SEPTEMBER, 23, year, Calendar.OCTOBER, 22, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.SCORPIO,
DateTimeUtils.getRange(year, Calendar.OCTOBER, 23, year, Calendar.NOVEMBER, 21)));
DateTimeUtils.getRange(year, Calendar.OCTOBER, 23, year, Calendar.NOVEMBER, 21, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.SAGITTARIUS,
DateTimeUtils.getRange(year, Calendar.NOVEMBER, 22, year, Calendar.DECEMBER, 21)));
DateTimeUtils.getRange(year, Calendar.NOVEMBER, 22, year, Calendar.DECEMBER, 21, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.CAPRICORN,
DateTimeUtils.getRange(year, Calendar.DECEMBER, 22, year + 1, Calendar.JANUARY, 19)));
DateTimeUtils.getRange(year, Calendar.DECEMBER, 22, year + 1, Calendar.JANUARY, 19, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.CAPRICORN,
DateTimeUtils.getRange(year - 1, Calendar.DECEMBER, 22, year, Calendar.JANUARY, 19)));
DateTimeUtils.getRange(year - 1, Calendar.DECEMBER, 22, year, Calendar.JANUARY, 19, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.AQUARIUS,
DateTimeUtils.getRange(year, Calendar.JANUARY, 20, year, Calendar.FEBRUARY, 18)));
DateTimeUtils.getRange(year, Calendar.JANUARY, 20, year, Calendar.FEBRUARY, 18, zone, locale)));
zodiacs.add(new SunZodiac(ZodiacSign.PISCES,
DateTimeUtils.getRange(year, Calendar.FEBRUARY, 19, year, Calendar.MARCH, 20)));
DateTimeUtils.getRange(year, Calendar.FEBRUARY, 19, year, Calendar.MARCH, 20, zone, locale)));
return zodiacs;
}

View File

@ -59,8 +59,9 @@ public class AstroDiscoveryService extends AbstractDiscoveryService {
private final LocationProvider locationProvider;
// All access must be guarded by "this"
private @Nullable ScheduledFuture<?> astroDiscoveryJob;
private @Nullable PointType previousLocation;
private volatile @Nullable PointType previousLocation;
@Activate
public AstroDiscoveryService(final @Reference LocationProvider locationProvider,
@ -85,7 +86,7 @@ public class AstroDiscoveryService extends AbstractDiscoveryService {
}
@Override
protected void startBackgroundDiscovery() {
protected synchronized void startBackgroundDiscovery() {
if (astroDiscoveryJob == null) {
astroDiscoveryJob = scheduler.scheduleWithFixedDelay(() -> {
PointType currentLocation = locationProvider.getLocation();
@ -103,11 +104,13 @@ public class AstroDiscoveryService extends AbstractDiscoveryService {
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping Astro device background discovery");
ScheduledFuture<?> discoveryJob = astroDiscoveryJob;
if (discoveryJob != null) {
discoveryJob.cancel(true);
synchronized (this) {
ScheduledFuture<?> discoveryJob = astroDiscoveryJob;
if (discoveryJob != null) {
discoveryJob.cancel(true);
}
astroDiscoveryJob = null;
}
astroDiscoveryJob = null;
}
public void createResults(PointType location) {

View File

@ -26,7 +26,9 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@ -44,6 +46,7 @@ import org.openhab.binding.astro.internal.job.PositionalJob;
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.util.PropertyUtils;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.scheduler.CronScheduler;
@ -77,20 +80,27 @@ public abstract class AstroThingHandler extends BaseThingHandler {
protected final TimeZoneProvider timeZoneProvider;
protected final LocaleProvider localeProvider;
private final Lock monitor = new ReentrantLock();
// All access must be guarded by "monitor"
private final Set<ScheduledFuture<?>> scheduledFutures = new HashSet<>();
// All access must be guarded by "monitor"
private boolean linkedPositionalChannels;
protected AstroThingConfig thingConfig = new AstroThingConfig();
// All access must be guarded by "monitor"
private @Nullable ScheduledCompletableFuture<?> dailyJob;
public AstroThingHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
public AstroThingHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider,
LocaleProvider localeProvider) {
super(thing);
this.cronScheduler = scheduler;
this.timeZoneProvider = timeZoneProvider;
this.localeProvider = localeProvider;
}
@Override
@ -175,8 +185,8 @@ public abstract class AstroThingHandler extends BaseThingHandler {
}
try {
AstroChannelConfig config = channel.getConfiguration().as(AstroChannelConfig.class);
updateState(channelUID,
PropertyUtils.getState(channelUID, config, planet, timeZoneProvider.getTimeZone()));
updateState(channelUID, PropertyUtils.getState(channelUID, config, planet,
TimeZone.getTimeZone(timeZoneProvider.getTimeZone())));
} catch (Exception ex) {
logger.error("Can't update state for channel {} : {}", channelUID, ex.getMessage(), ex);
}
@ -193,9 +203,10 @@ public abstract class AstroThingHandler extends BaseThingHandler {
try {
stopJobs();
if (getThing().getStatus() == ONLINE) {
String thingUID = getThing().getUID().toString();
TimeZone zone = TimeZone.getTimeZone(timeZoneProvider.getTimeZone());
Locale locale = localeProvider.getLocale();
// Daily Job
Job runnable = getDailyJob();
Job runnable = getDailyJob(zone, locale);
dailyJob = cronScheduler.schedule(runnable, DAILY_MIDNIGHT);
logger.debug("Scheduled {} at midnight", dailyJob);
// Execute daily startup job immediately
@ -205,7 +216,7 @@ public abstract class AstroThingHandler extends BaseThingHandler {
// Use scheduleAtFixedRate to avoid time drift associated with scheduleWithFixedDelay
linkedPositionalChannels = isPositionalChannelLinked();
if (linkedPositionalChannels) {
Job positionalJob = new PositionalJob(thingUID);
Job positionalJob = new PositionalJob(this);
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(positionalJob, 0, thingConfig.interval,
TimeUnit.SECONDS);
scheduledFutures.add(future);
@ -258,10 +269,16 @@ public abstract class AstroThingHandler extends BaseThingHandler {
*/
private void linkedChannelChange(ChannelUID channelUID) {
if (Arrays.asList(getPositionalChannelIds()).contains(channelUID.getId())) {
boolean oldValue = linkedPositionalChannels;
linkedPositionalChannels = isPositionalChannelLinked();
if (oldValue != linkedPositionalChannels) {
restartJobs();
boolean newValue = isPositionalChannelLinked();
monitor.lock();
try {
boolean oldValue = linkedPositionalChannels;
linkedPositionalChannels = newValue;
if (oldValue != linkedPositionalChannels) {
restartJobs();
}
} finally {
monitor.unlock();
}
}
}
@ -313,12 +330,17 @@ public abstract class AstroThingHandler extends BaseThingHandler {
}
private void tidyScheduledFutures() {
for (Iterator<ScheduledFuture<?>> iterator = scheduledFutures.iterator(); iterator.hasNext();) {
ScheduledFuture<?> future = iterator.next();
if (future.isDone()) {
logger.trace("Tidying up done future {}", future);
iterator.remove();
monitor.lock();
try {
for (Iterator<ScheduledFuture<?>> iterator = scheduledFutures.iterator(); iterator.hasNext();) {
ScheduledFuture<?> future = iterator.next();
if (future.isDone()) {
logger.trace("Tidying up done future {}", future);
iterator.remove();
}
}
} finally {
monitor.unlock();
}
}
@ -347,7 +369,7 @@ public abstract class AstroThingHandler extends BaseThingHandler {
/**
* Returns the daily calculation {@link Job} (cannot be {@code null})
*/
protected abstract Job getDailyJob();
protected abstract Job getDailyJob(TimeZone zone, Locale locale);
public abstract @Nullable Position getPositionAt(ZonedDateTime date);

View File

@ -12,9 +12,12 @@
*/
package org.openhab.binding.astro.internal.handler;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -24,6 +27,7 @@ import org.openhab.binding.astro.internal.job.Job;
import org.openhab.binding.astro.internal.model.Moon;
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
@ -40,24 +44,29 @@ public class MoonHandler extends AstroThingHandler {
private final String[] positionalChannelIds = new String[] { "phase#name", "phase#age", "phase#agePercent",
"phase#ageDegree", "phase#illumination", "position#azimuth", "position#elevation", "zodiac#sign" };
private final MoonCalc moonCalc = new MoonCalc();
private @NonNullByDefault({}) Moon moon;
private volatile @Nullable Moon moon;
/**
* Constructor
*/
public MoonHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
super(thing, scheduler, timeZoneProvider);
public MoonHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider,
LocaleProvider localeProvider) {
super(thing, scheduler, timeZoneProvider, localeProvider);
}
@Override
public void publishPositionalInfo() {
moon = getMoonAt(ZonedDateTime.now());
ZoneId zoneId = timeZoneProvider.getTimeZone();
TimeZone zone = TimeZone.getTimeZone(zoneId);
Locale locale = localeProvider.getLocale();
Moon moon = getMoonAt(ZonedDateTime.now(zoneId), locale);
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
moonCalc.setPositionalInfo(Calendar.getInstance(), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, moon);
moonCalc.setPositionalInfo(Calendar.getInstance(zone, locale), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, moon, zone, locale);
moon.getEclipse().setElevations(this, timeZoneProvider);
this.moon = moon;
publishPlanet();
}
@ -79,24 +88,24 @@ public class MoonHandler extends AstroThingHandler {
}
@Override
protected Job getDailyJob() {
return new DailyJobMoon(thing.getUID().getAsString(), this);
protected Job getDailyJob(TimeZone zone, Locale locale) {
return new DailyJobMoon(this, zone, locale);
}
private Moon getMoonAt(ZonedDateTime date) {
private Moon getMoonAt(ZonedDateTime date, Locale locale) {
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
return moonCalc.getMoonInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0,
longitude != null ? longitude : 0);
longitude != null ? longitude : 0, TimeZone.getTimeZone(date.getZone()), locale);
}
@Override
public @Nullable Position getPositionAt(ZonedDateTime date) {
Moon localMoon = getMoonAt(date);
Moon localMoon = getMoonAt(date, Locale.ROOT);
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
moonCalc.setPositionalInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, localMoon);
longitude != null ? longitude : 0, localMoon, TimeZone.getTimeZone(date.getZone()), Locale.ROOT);
return localMoon.getPosition();
}
}

View File

@ -12,9 +12,12 @@
*/
package org.openhab.binding.astro.internal.handler;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -27,6 +30,7 @@ import org.openhab.binding.astro.internal.model.Radiation;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPhaseName;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
@ -43,25 +47,30 @@ public class SunHandler extends AstroThingHandler {
private final String[] positionalChannelIds = new String[] { "position#azimuth", "position#elevation",
"radiation#direct", "radiation#diffuse", "radiation#total" };
private final SunCalc sunCalc = new SunCalc();
private @NonNullByDefault({}) Sun sun;
private volatile @Nullable Sun sun;
/**
* Constructor
*/
public SunHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
super(thing, scheduler, timeZoneProvider);
public SunHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider,
LocaleProvider localeProvider) {
super(thing, scheduler, timeZoneProvider, localeProvider);
}
@Override
public void publishPositionalInfo() {
sun = getSunAt(ZonedDateTime.now());
ZoneId zoneId = timeZoneProvider.getTimeZone();
TimeZone zone = TimeZone.getTimeZone(zoneId);
Locale locale = localeProvider.getLocale();
Sun sun = getSunAt(ZonedDateTime.now(zoneId));
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
Double altitude = thingConfig.altitude;
sunCalc.setPositionalInfo(Calendar.getInstance(), latitude != null ? latitude : 0,
sunCalc.setPositionalInfo(Calendar.getInstance(zone, locale), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, altitude != null ? altitude : 0, sun);
sun.getEclipse().setElevations(this, timeZoneProvider);
this.sun = sun;
publishPlanet();
}
@ -83,8 +92,8 @@ public class SunHandler extends AstroThingHandler {
}
@Override
protected Job getDailyJob() {
return new DailyJobSun(thing.getUID().getAsString(), this);
protected Job getDailyJob(TimeZone zone, Locale locale) {
return new DailyJobSun(this, zone, locale);
}
private Sun getSunAt(ZonedDateTime date) {
@ -92,8 +101,8 @@ public class SunHandler extends AstroThingHandler {
Double longitude = thingConfig.longitude;
Double altitude = thingConfig.altitude;
return sunCalc.getSunInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, altitude != null ? altitude : 0,
thingConfig.useMeteorologicalSeason);
longitude != null ? longitude : 0, altitude != null ? altitude : 0, thingConfig.useMeteorologicalSeason,
TimeZone.getTimeZone(timeZoneProvider.getTimeZone()), Locale.ROOT);
}
private Sun getPositionedSunAt(ZonedDateTime date) {
@ -110,7 +119,7 @@ public class SunHandler extends AstroThingHandler {
Range eventRange = getSunAt(date).getAllRanges().get(sunPhase);
if (eventRange != null) {
Calendar cal = begin ? eventRange.getStart() : eventRange.getEnd();
return ZonedDateTime.ofInstant(cal.toInstant(), date.getZone());
return cal == null ? null : ZonedDateTime.ofInstant(cal.toInstant(), date.getZone());
} else {
return null;
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.astro.internal.job;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
/**
* This class contains the default methods required for different jobs
@ -22,15 +23,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@NonNullByDefault
public abstract class AbstractJob implements Job {
private final String thingUID;
protected final AstroThingHandler handler;
public AbstractJob(String thingUID) {
this.thingUID = thingUID;
public AbstractJob(AstroThingHandler handler) {
this.handler = handler;
}
@Override
public String getThingUID() {
return thingUID;
public AstroThingHandler getHandler() {
return handler;
}
/**

View File

@ -16,6 +16,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
/**
* {@link CompositeJob} comprises multiple {@link Job}s to be executed in order
@ -31,18 +32,18 @@ public final class CompositeJob extends AbstractJob {
/**
* Constructor
*
* @param thingUID thing UID
* @param handler thing thing handler
* @param jobs the jobs to execute
* @throws IllegalArgumentException
* if {@code jobs} is {@code null} or empty
*/
public CompositeJob(String thingUID, List<Job> jobs) {
super(thingUID);
public CompositeJob(AstroThingHandler handler, List<Job> jobs) {
super(handler);
this.jobs = jobs;
this.jobs = List.copyOf(jobs);
boolean notMatched = jobs.stream().anyMatch(j -> !j.getThingUID().equals(thingUID));
checkArgument(!notMatched, "The jobs must associate the same thing UID");
boolean notMatched = jobs.stream().anyMatch(j -> !j.getHandler().equals(handler));
checkArgument(!notMatched, "The jobs must associate the same thing handler");
}
@Override
@ -50,8 +51,9 @@ public final class CompositeJob extends AbstractJob {
jobs.forEach(j -> {
try {
j.run();
} catch (RuntimeException ex) {
LOGGER.warn("Job execution failed.", ex);
} catch (Exception e) {
logger.warn("Job execution of \"{}\" failed: {}", j, e.getMessage());
logger.trace("", e);
}
});
}

View File

@ -16,6 +16,8 @@ import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import static org.openhab.binding.astro.internal.job.Job.scheduleEvent;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
@ -33,57 +35,90 @@ import org.openhab.binding.astro.internal.model.Planet;
@NonNullByDefault
public final class DailyJobMoon extends AbstractJob {
private final AstroThingHandler handler;
public final TimeZone zone;
public final Locale locale;
/**
* Constructor
*
* @param thingUID the Thing UID
* @param handler the {@link AstroThingHandler} instance
* @throws IllegalArgumentException if {@code thingUID} or {@code handler} is {@code null}
*/
public DailyJobMoon(String thingUID, AstroThingHandler handler) {
super(thingUID);
this.handler = handler;
public DailyJobMoon(AstroThingHandler handler, TimeZone zone, Locale locale) {
super(handler);
this.zone = zone;
this.locale = locale;
}
@Override
public void run() {
handler.publishDailyInfo();
String thingUID = getThingUID();
LOGGER.debug("Scheduled Astro event-jobs for thing {}", thingUID);
Planet planet = handler.getPlanet();
if (planet == null) {
LOGGER.error("Planet not instantiated");
return;
}
Moon moon = (Moon) planet;
scheduleEvent(thingUID, handler, moon.getRise().getStart(), EVENT_START, EVENT_CHANNEL_ID_RISE, false);
scheduleEvent(thingUID, handler, moon.getSet().getEnd(), EVENT_END, EVENT_CHANNEL_ID_SET, false);
MoonPhase moonPhase = moon.getPhase();
scheduleEvent(thingUID, handler, moonPhase.getFirstQuarter(), EVENT_PHASE_FIRST_QUARTER,
EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getThirdQuarter(), EVENT_PHASE_THIRD_QUARTER,
EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getFull(), EVENT_PHASE_FULL, EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getNew(), EVENT_PHASE_NEW, EVENT_CHANNEL_ID_MOON_PHASE, false);
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(thingUID, handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false);
try {
handler.publishDailyInfo();
if (logger.isDebugEnabled()) {
logger.debug("Scheduled Astro event-jobs for thing {}", handler.getThing().getUID());
}
});
scheduleEvent(thingUID, handler, moon.getPerigee().getDate(), EVENT_PERIGEE, EVENT_CHANNEL_ID_PERIGEE, false);
scheduleEvent(thingUID, handler, moon.getApogee().getDate(), EVENT_APOGEE, EVENT_CHANNEL_ID_APOGEE, false);
Planet planet = handler.getPlanet();
if (planet == null) {
logger.error("Planet not instantiated");
return;
}
Moon moon = (Moon) planet;
Calendar cal = moon.getRise().getStart();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_START, EVENT_CHANNEL_ID_RISE, false, zone, locale);
}
cal = moon.getSet().getEnd();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_END, EVENT_CHANNEL_ID_SET, false, zone, locale);
}
MoonPhase moonPhase = moon.getPhase();
cal = moonPhase.getFirstQuarter();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_PHASE_FIRST_QUARTER, EVENT_CHANNEL_ID_MOON_PHASE, false, zone,
locale);
}
cal = moonPhase.getThirdQuarter();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_PHASE_THIRD_QUARTER, EVENT_CHANNEL_ID_MOON_PHASE, false, zone,
locale);
}
cal = moonPhase.getFull();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_PHASE_FULL, EVENT_CHANNEL_ID_MOON_PHASE, false, zone, locale);
}
cal = moonPhase.getNew();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_PHASE_NEW, EVENT_CHANNEL_ID_MOON_PHASE, false, zone, locale);
}
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false, zone,
locale);
}
});
cal = moon.getPerigee().getDate();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_PERIGEE, EVENT_CHANNEL_ID_PERIGEE, false, zone, locale);
}
cal = moon.getApogee().getDate();
if (cal != null) {
scheduleEvent(handler, cal, EVENT_APOGEE, EVENT_CHANNEL_ID_APOGEE, false, zone, locale);
}
} catch (Exception e) {
logger.warn("The daily moon job execution for \"{}\" failed: {}", handler.getThing().getUID(),
e.getMessage());
logger.trace("", e);
}
}
@Override
public String toString() {
return "Daily job moon " + getThingUID();
return "Daily job moon " + handler.getThing().getUID();
}
}

View File

@ -17,12 +17,16 @@ import static org.openhab.binding.astro.internal.job.Job.*;
import static org.openhab.binding.astro.internal.model.SunPhaseName.*;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunZodiac;
/**
* Daily scheduled jobs For Sun planet
@ -33,74 +37,150 @@ import org.openhab.binding.astro.internal.model.Sun;
@NonNullByDefault
public final class DailyJobSun extends AbstractJob {
private final AstroThingHandler handler;
private final TimeZone zone;
private final Locale locale;
/**
* Constructor
*
* @param thingUID the Thing UID
* @param handler the {@link AstroThingHandler} instance
* @throws IllegalArgumentException
* if {@code thingUID} or {@code handler} is {@code null}
*/
public DailyJobSun(String thingUID, AstroThingHandler handler) {
super(thingUID);
this.handler = handler;
public DailyJobSun(AstroThingHandler handler, TimeZone zone, Locale locale) {
super(handler);
this.zone = zone;
this.locale = locale;
}
@Override
public void run() {
handler.publishDailyInfo();
String thingUID = getThingUID();
LOGGER.debug("Scheduled Astro event-jobs for thing {}", thingUID);
Planet planet = handler.getPlanet();
if (planet == null) {
LOGGER.error("Planet not instantiated");
return;
}
Sun sun = (Sun) planet;
scheduleRange(thingUID, handler, sun.getRise(), EVENT_CHANNEL_ID_RISE);
scheduleRange(thingUID, handler, sun.getSet(), EVENT_CHANNEL_ID_SET);
scheduleRange(thingUID, handler, sun.getNoon(), EVENT_CHANNEL_ID_NOON);
scheduleRange(thingUID, handler, sun.getNight(), EVENT_CHANNEL_ID_NIGHT);
scheduleRange(thingUID, handler, sun.getMorningNight(), EVENT_CHANNEL_ID_MORNING_NIGHT);
scheduleRange(thingUID, handler, sun.getAstroDawn(), EVENT_CHANNEL_ID_ASTRO_DAWN);
scheduleRange(thingUID, handler, sun.getNauticDawn(), EVENT_CHANNEL_ID_NAUTIC_DAWN);
scheduleRange(thingUID, handler, sun.getCivilDawn(), EVENT_CHANNEL_ID_CIVIL_DAWN);
scheduleRange(thingUID, handler, sun.getAstroDusk(), EVENT_CHANNEL_ID_ASTRO_DUSK);
scheduleRange(thingUID, handler, sun.getNauticDusk(), EVENT_CHANNEL_ID_NAUTIC_DUSK);
scheduleRange(thingUID, handler, sun.getCivilDusk(), EVENT_CHANNEL_ID_CIVIL_DUSK);
scheduleRange(thingUID, handler, sun.getEveningNight(), EVENT_CHANNEL_ID_EVENING_NIGHT);
scheduleRange(thingUID, handler, sun.getDaylight(), EVENT_CHANNEL_ID_DAYLIGHT);
Eclipse eclipse = sun.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(thingUID, handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false);
try {
handler.publishDailyInfo();
if (logger.isDebugEnabled()) {
logger.debug("Scheduled Astro event-jobs for thing {}", handler.getThing().getUID());
}
});
// schedule republish jobs
schedulePublishPlanet(thingUID, handler, sun.getZodiac().getEnd());
schedulePublishPlanet(thingUID, handler, sun.getSeason().getNextSeason());
Planet planet = handler.getPlanet();
if (planet == null) {
logger.error("Planet not instantiated");
return;
}
Sun sun = (Sun) planet;
scheduleRange(handler, sun.getRise(), EVENT_CHANNEL_ID_RISE, zone, locale);
scheduleRange(handler, sun.getSet(), EVENT_CHANNEL_ID_SET, zone, locale);
Range range = sun.getNoon();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_NOON, zone, locale);
}
range = sun.getNight();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_NIGHT, zone, locale);
}
range = sun.getMorningNight();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_MORNING_NIGHT, zone, locale);
}
range = sun.getAstroDawn();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_ASTRO_DAWN, zone, locale);
}
range = sun.getNauticDawn();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_NAUTIC_DAWN, zone, locale);
}
range = sun.getCivilDawn();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_CIVIL_DAWN, zone, locale);
}
range = sun.getAstroDusk();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_ASTRO_DUSK, zone, locale);
}
range = sun.getNauticDusk();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_NAUTIC_DUSK, zone, locale);
}
range = sun.getCivilDusk();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_CIVIL_DUSK, zone, locale);
}
range = sun.getEveningNight();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_EVENING_NIGHT, zone, locale);
}
range = sun.getDaylight();
if (range != null) {
scheduleRange(handler, range, EVENT_CHANNEL_ID_DAYLIGHT, zone, locale);
}
// schedule phase jobs
scheduleSunPhase(thingUID, handler, SUN_RISE, sun.getRise().getStart());
scheduleSunPhase(thingUID, handler, SUN_SET, sun.getSet().getStart());
scheduleSunPhase(thingUID, handler, NIGHT, sun.getNight().getStart());
scheduleSunPhase(thingUID, handler, DAYLIGHT, sun.getDaylight().getStart());
scheduleSunPhase(thingUID, handler, ASTRO_DAWN, sun.getAstroDawn().getStart());
scheduleSunPhase(thingUID, handler, NAUTIC_DAWN, sun.getNauticDawn().getStart());
scheduleSunPhase(thingUID, handler, CIVIL_DAWN, sun.getCivilDawn().getStart());
scheduleSunPhase(thingUID, handler, ASTRO_DUSK, sun.getAstroDusk().getStart());
scheduleSunPhase(thingUID, handler, NAUTIC_DUSK, sun.getNauticDusk().getStart());
scheduleSunPhase(thingUID, handler, CIVIL_DUSK, sun.getCivilDusk().getStart());
Eclipse eclipse = sun.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false, zone,
locale);
}
});
// schedule republish jobs
SunZodiac sunZodiac;
Calendar cal = (sunZodiac = sun.getZodiac()) == null ? null : sunZodiac.getEnd();
if (cal != null) {
schedulePublishPlanet(handler, cal, zone, locale);
}
schedulePublishPlanet(handler, sun.getSeason().getNextSeason(zone, locale), zone, locale);
// schedule phase jobs
cal = sun.getRise().getStart();
if (cal != null) {
scheduleSunPhase(handler, SUN_RISE, cal, zone, locale);
}
cal = sun.getSet().getStart();
if (cal != null) {
scheduleSunPhase(handler, SUN_SET, cal, zone, locale);
}
cal = (range = sun.getNight()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, NIGHT, cal, zone, locale);
}
cal = (range = sun.getDaylight()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, DAYLIGHT, cal, zone, locale);
}
cal = (range = sun.getAstroDawn()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, ASTRO_DAWN, cal, zone, locale);
}
cal = (range = sun.getNauticDawn()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, NAUTIC_DAWN, cal, zone, locale);
}
cal = (range = sun.getCivilDawn()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, CIVIL_DAWN, cal, zone, locale);
}
cal = (range = sun.getAstroDusk()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, ASTRO_DUSK, cal, zone, locale);
}
cal = (range = sun.getNauticDusk()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, NAUTIC_DUSK, cal, zone, locale);
}
cal = (range = sun.getCivilDusk()) == null ? null : range.getStart();
if (cal != null) {
scheduleSunPhase(handler, CIVIL_DUSK, cal, zone, locale);
}
} catch (Exception e) {
logger.warn("The daily sun job execution for \"{}\" failed: {}", handler.getThing().getUID(),
e.getMessage());
logger.trace("", e);
}
}
@Override
public String toString() {
return "Daily job sun " + getThingUID();
return "Daily job sun " + handler.getThing().getUID();
}
}

Some files were not shown because too many files have changed in this diff Show More