Compare commits
221 Commits
Author | SHA1 | Date |
---|---|---|
|
5ba98f0c5f | |
|
e79d3a6a76 | |
|
cbe003ac59 | |
|
b389bafe44 | |
|
08d5c8e385 | |
|
48485705e6 | |
|
cd639eafef | |
|
c42047b636 | |
|
6393b850eb | |
|
449fc06bbc | |
|
2247c2b825 | |
|
0d2c48fc7a | |
|
c618ffa88b | |
|
2a972c7a45 | |
|
131e317c5d | |
|
07f232af39 | |
|
c4dceefccd | |
|
3942fd29da | |
|
cbfff2b2cd | |
|
93cc3ab06c | |
|
2ccf6bb54d | |
|
d0efcd78a7 | |
|
deef965aad | |
|
073ed7e3f8 | |
|
424b01ba8c | |
|
8b0b1f42ec | |
|
5a48ca6b73 | |
|
4176647972 | |
|
8725638abd | |
|
2808642984 | |
|
beba61421e | |
|
3034465f6c | |
|
f817cb086a | |
|
0b2e2cf3d1 | |
|
43afc5f5f1 | |
|
4d15e99032 | |
|
7c80002e7d | |
|
a5381d1fed | |
|
a5fcfe40c4 | |
|
ea276c759a | |
|
94d7c15e22 | |
|
ef8976982e | |
|
458fc9550f | |
|
280bb38029 | |
|
94e771e4f7 | |
|
8cf841df14 | |
|
28610e05bc | |
|
ddea03f1e5 | |
|
f3ab7c89a3 | |
|
51963af22c | |
|
46a858191d | |
|
5d38b6e364 | |
|
539f52715c | |
|
c92509de7c | |
|
f2b8f2674c | |
|
6758592249 | |
|
2bdca5517f | |
|
9dc6e48174 | |
|
ce80a9861f | |
|
5d6409f5d8 | |
|
8efd3990f8 | |
|
2966d33fc1 | |
|
2f3c291b9d | |
|
654be3d89c | |
|
b4d83ab930 | |
|
6ab5d848e8 | |
|
887a842c03 | |
|
1c61b66f1d | |
|
b28715570f | |
|
4da0fee5b3 | |
|
f899b8802b | |
|
c777ccf38c | |
|
5e5a0a49af | |
|
fda5a9ce1a | |
|
0e37ae1ed2 | |
|
c85037e10c | |
|
b10918c91f | |
|
15bca863ef | |
|
4bf7372892 | |
|
093a80607d | |
|
c698d0c5ae | |
|
c417fe3f2f | |
|
12b1757874 | |
|
761836dca3 | |
|
d748f32c65 | |
|
687e283ff3 | |
|
7a158761e6 | |
|
f8fed2b938 | |
|
1c5f750e97 | |
|
43e804f179 | |
|
03058507be | |
|
0b45e88932 | |
|
5c1762d19c | |
|
c7da9f4216 | |
|
0d36961494 | |
|
c9d3bb7a2f | |
|
25160cb40f | |
|
5bab3f4941 | |
|
a2a637c305 | |
|
441bf584a7 | |
|
1aa39b743a | |
|
fa5ee6a6ba | |
|
595655f19c | |
|
71342e3df8 | |
|
befdc91969 | |
|
d982d4d1da | |
|
cdbac5cc52 | |
|
3c075cbb2d | |
|
a190fe1e96 | |
|
cfea7651fc | |
|
02e7b1c252 | |
|
3b058caeb9 | |
|
5bb7ef59c0 | |
|
0c6a72df58 | |
|
f54ff70aaf | |
|
07b11edbbd | |
|
605e9ba668 | |
|
05aa010ad8 | |
|
8cae3d5676 | |
|
e65acf1534 | |
|
2a1ee509e9 | |
|
787242d982 | |
|
4d403ea438 | |
|
24747129e6 | |
|
0d28575470 | |
|
ce138b5ffd | |
|
f871501135 | |
|
4cdd798205 | |
|
3b9017bfb0 | |
|
866bc99a31 | |
|
3e66c13f0e | |
|
5181b7df1b | |
|
46c60091d3 | |
|
f307850a10 | |
|
8ae35c0c84 | |
|
8ebd608060 | |
|
7433801d22 | |
|
f9f2a24191 | |
|
58973f9cf2 | |
|
830db535f4 | |
|
09c59ac415 | |
|
294cfc4963 | |
|
b3b1cbc6d7 | |
|
83c1182889 | |
|
632e15b227 | |
|
173c7600d9 | |
|
5ccd1859e3 | |
|
b57b1caf60 | |
|
a358bf83dd | |
|
442589cafe | |
|
d14f74b9e9 | |
|
533ed4c18f | |
|
2cb32ae44c | |
|
e47e6dea8e | |
|
eef2ed1ea3 | |
|
093d4948f0 | |
|
3725576ade | |
|
976a4acd75 | |
|
1e13bef624 | |
|
fe5414ff5c | |
|
929a367e7b | |
|
8bab650303 | |
|
0180b9437b | |
|
4d0a04aa87 | |
|
5acfef3f97 | |
|
0d1dcd6133 | |
|
3fc91e5e92 | |
|
26450bc6fc | |
|
cf671c31b0 | |
|
45709e089c | |
|
8eda464a0e | |
|
0d76f68c96 | |
|
4eb6bad3be | |
|
8b26f7d3bd | |
|
0421d6e8f9 | |
|
04d8143026 | |
|
834b587623 | |
|
5c817ea190 | |
|
12ea895db2 | |
|
a4f2c1546e | |
|
9440628914 | |
|
b8721e7f64 | |
|
ffbaad93d7 | |
|
901927d0a2 | |
|
6304dbc7b6 | |
|
cc45e8f31e | |
|
bdcde5a1ef | |
|
c85837c371 | |
|
6603704bc9 | |
|
090d1e13a2 | |
|
4fc4b35d99 | |
|
a3f8c72bef | |
|
73c92070e3 | |
|
6349db2df3 | |
|
280a6db9e8 | |
|
a468112ad5 | |
|
a8ce93f4d5 | |
|
cadb8cb298 | |
|
f34491551d | |
|
9189ca5c4e | |
|
8fa366d119 | |
|
6c45f08eca | |
|
18d288b679 | |
|
e458413289 | |
|
1b89ea87fb | |
|
521a1da093 | |
|
e6bd19f0f2 | |
|
cd139da43b | |
|
ffc5ba6e46 | |
|
445c17891b | |
|
e8b5839c5d | |
|
1d8f6f4e89 | |
|
bf85797032 | |
|
ce67b0145e | |
|
6592ccaa18 | |
|
7e3b22cd78 | |
|
d64a011ab4 | |
|
446c8c089f | |
|
5d0339ab09 | |
|
90b3d12aa3 | |
|
d779c17335 |
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
10
CODEOWNERS
10
CODEOWNERS
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
27
README.md
27
README.md
|
@ -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" />
|
||||
|
||||
[](https://github.com/openhab/openhab-addons/actions/workflows/ci-build.yml)
|
||||
[](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!
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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%
|
||||
|
|
|
@ -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:]'`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"}
|
||||
```
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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...." ] {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |  |
|
||||
| oh:airparif:alder | Yes |  |
|
||||
| oh:airparif:ash | Yes |  |
|
||||
| oh:airparif:birch | Yes |  |
|
||||
| oh:airparif:chestnut | Yes |  |
|
||||
| oh:airparif:cypress | Yes |  |
|
||||
| oh:airparif:grasses | Yes |  |
|
||||
| oh:airparif:hazel | Yes |  |
|
||||
| oh:airparif:hornbeam | Yes |  |
|
||||
| oh:airparif:linden | Yes |  |
|
||||
| oh:airparif:oak | Yes |  |
|
||||
| oh:airparif:olive | Yes |  |
|
||||
| oh:airparif:plane | Yes |  |
|
||||
| oh:airparif:plantain | Yes |  |
|
||||
| oh:airparif:aq | Yes |  |
|
||||
| oh:airparif:alder | Yes |  |
|
||||
| oh:airparif:ash | Yes |  |
|
||||
| oh:airparif:birch | Yes |  |
|
||||
| oh:airparif:chestnut | Yes |  |
|
||||
| oh:airparif:cypress | Yes |  |
|
||||
| oh:airparif:grasses | Yes |  |
|
||||
| oh:airparif:hazel | Yes |  |
|
||||
| oh:airparif:hornbeam | Yes |  |
|
||||
| oh:airparif:linden | Yes |  |
|
||||
| oh:airparif:oak | Yes |  |
|
||||
| oh:airparif:olive | Yes |  |
|
||||
| oh:airparif:plane | Yes |  |
|
||||
| oh:airparif:plantain | Yes |  |
|
||||
| oh:airparif:pollen | Yes | x |
|
||||
| oh:airparif:poplar | Yes |  |
|
||||
| oh:airparif:ragweed | Yes |  |
|
||||
| oh:airparif:rumex | Yes |  |
|
||||
| oh:airparif:urticaceae | Yes |  |
|
||||
| oh:airparif:willow | Yes |  |
|
||||
| oh:airparif:wormwood | Yes |  |
|
||||
| oh:airparif:poplar | Yes |  |
|
||||
| oh:airparif:ragweed | Yes |  |
|
||||
| oh:airparif:rumex | Yes |  |
|
||||
| oh:airparif:urticaceae | Yes |  |
|
||||
| oh:airparif:willow | Yes |  |
|
||||
| oh:airparif:wormwood | Yes |  |
|
||||
|
||||
## Full Examplee
|
||||
## Full Example
|
||||
|
||||
### Thing Configurationn
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
||||
|------|--------|--------------------------------|
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 it’s 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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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> |
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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!
|
||||
|
||||

|
||||
|
@ -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!
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue