mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[juicenet] Initial contribution (#10768)
Signed-off-by: Jeff James <jeff@james-online.com>
This commit is contained in:
parent
fbd06ec709
commit
71d1226505
@ -153,6 +153,7 @@
|
||||
/bundles/org.openhab.binding.jablotron/ @octa22
|
||||
/bundles/org.openhab.binding.jeelink/ @vbier
|
||||
/bundles/org.openhab.binding.jellyfin/ @GiviMAD
|
||||
/bundles/org.openhab.binding.juicenet/ @jsjames
|
||||
/bundles/org.openhab.binding.kaleidescape/ @mlobstein
|
||||
/bundles/org.openhab.binding.keba/ @kgoderis
|
||||
/bundles/org.openhab.binding.km200/ @Markinus
|
||||
|
@ -761,6 +761,11 @@
|
||||
<artifactId>org.openhab.binding.jellyfin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.juicenet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.kaleidescape</artifactId>
|
||||
|
13
bundles/org.openhab.binding.juicenet/NOTICE
Normal file
13
bundles/org.openhab.binding.juicenet/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
270
bundles/org.openhab.binding.juicenet/README.md
Normal file
270
bundles/org.openhab.binding.juicenet/README.md
Normal file
@ -0,0 +1,270 @@
|
||||
# JuiceNet Binding
|
||||
|
||||
The JuiceNet binding will interface with the cloud portal to get status and manage your JuiceBox EV charger(s).
|
||||
In addition to getting the status of various items from the EV charger, it is also possible to start and stop charging sessions.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports the following things:
|
||||
|
||||
| thing | type | description |
|
||||
|---------- |-------- |------------------------------ |
|
||||
| JuiceNet Account | Bridge | This represents the cloud account to interface with the JuiceNet API. |
|
||||
| JuiceBox EV Charger | Device | This interfaces to a specific JuiceBox EV charger associated with the JuiceNet account. |
|
||||
|
||||
This binding should work with multiple JuiceBox EV chargers associated with the account, however it is currently only tested with a single EV charger.
|
||||
|
||||
### Discovery
|
||||
|
||||
Once a JuiceNet Account bridge has been created, any JuiceBox EV Chargers associated with this account will be discovered.
|
||||
|
||||
|
||||
### Thing Configuration
|
||||
|
||||
The configuration required is to create a JuiceNet account thing and fill in the appropriate API token.
|
||||
The API token can be found on the Account page at https://home.juice.net/Manage.
|
||||
|
||||
A JuiceBox EV Charger requires a a unitID which can also be found in the device settings at the JuiceNet web page.
|
||||
|
||||
## Channels
|
||||
|
||||
| channel | type | read-only | description |
|
||||
|---------- |-------- |--------- | ------- |
|
||||
| name | String | Y | Name of device.|
|
||||
| chargingState | String | N | Current charging state (Start Charging, Smart Charging, Stop Charging). |
|
||||
| state | String | Y | This is the current device state (Available, Plugged-In, Charging, Error, Disconnected). |
|
||||
| message | String | Y | This is a message detailing the state of the EV charger. |
|
||||
| override | Switch | Y | Smart charging is overridden. |
|
||||
| chargingTimeLeft | Number:Time | Y | Charging time left (seconds). |
|
||||
| plugUnplugTime | DateTime | Y | Last time of either plug-in or plug-out. |
|
||||
| targetTime | DateTime | N | “Start charging” start time, or time to start when overriding smart charging. |
|
||||
| unitTime | DateTime | Y | Current time on the unit. |
|
||||
| temperature | Number:Temperature | Y | Current temperature at the unit. |
|
||||
| currentLimit | Number:ElectricCurrent | N | Max charging current allowed. |
|
||||
| current | Number:ElectricCurrent | Y | Current charging current. |
|
||||
| voltage | Number:ElectricPotential | Y | Current voltage. |
|
||||
| energy | Number:Energy | Y | Current amount of energy poured to the vehicle. |
|
||||
| savings | Number | Y | Current session EV savings. |
|
||||
| power | Number:Power | Y | Current charging power. |
|
||||
| secondsCharging | Number:Time | Y | Charging time since plug-in time. |
|
||||
| energyAtPlugin | Number:Energy | Y | Energy value at the plugging time. |
|
||||
| energyToAdd | Number:Energy | N | Amount of energy to be added in current session. |
|
||||
| lifetimeEnergy | Number:Energy | Y | Total energy delivered to vehicles during lifetime. |
|
||||
| lifetimeSavings | Number | Y | EV driving saving during lifetime. |
|
||||
| gasCost | Number | Y | Cost of gasoline used in savings calculations. |
|
||||
| fuelConsumption | Number | Y | Miles per gallon used in savings calculations. |
|
||||
| ecost | Number | Y | Cost of electricity from utility company. (currency/kWh) |
|
||||
| energyPerMile | Number | Y | Energy per mile. |
|
||||
| carDescription | String | Y | Car description of vehicle currently or last charged. |
|
||||
| carBatterySize | Number:Energy | Y | Car battery pack size. |
|
||||
| carBatteryRange | Number:Length | Y | Car range. |
|
||||
| carChargingRate | Number:Power | Y | Car charging rate. |
|
||||
|
||||
## Full Example
|
||||
|
||||
### Things File
|
||||
|
||||
If configuring the binding with manual configuration an example thing file looks like this:
|
||||
|
||||
```
|
||||
Bridge juicenet:account:myaccount [ apiToken="xxxx-xxxx-xxxx-xxxx-xxxxx" ] {
|
||||
Thing device JamesCharger [ unitID="xxxxxxx" ]
|
||||
}
|
||||
```
|
||||
|
||||
### Items File
|
||||
|
||||
An example of an items file is here.
|
||||
|
||||
```
|
||||
String JuiceNet_Name "Name" { channel="juicenet:device:myaccount:JamesCharger:name" }
|
||||
String JuiceNet_State "Device State" { channel="juicenet:device:myaccount:JamesCharger:state" }
|
||||
String JuiceNet_ChargingState "Charging State" { channel="juicenet:device:myaccount:JamesCharger:chargingState" }
|
||||
String JuiceNet_Message "State Message" { channel="juicenet:device:myaccount:JamesCharger:message" }
|
||||
Switch JuiceNet_Override "Override State" { channel="juicenet:device:myaccount:JamesCharger:override" }
|
||||
DateTime JuiceNet_PlutUnplugTime "Plug/Unplug Time [%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp]" { channel="juicenet:device:myaccount:JamesCharger:plugUnplugTime" }
|
||||
DateTime JuiceNet_TargetTime "Target Time [%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp]" { channel="juicenet:device:myaccount:JamesCharger:targetTime" }
|
||||
Number:Time JuiceNet_ChargingTimeLeft "Charging Time Left [%.0f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:chargingTimeLeft" }
|
||||
DateTime JuiceNet_UnitTime "Unit Time [%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp]" { channel="juicenet:device:myaccount:JamesCharger:unitTime" }
|
||||
Number:Temperature JuiceNet_Temperature "Temperature [%.0f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:temperature" }
|
||||
Number:ElectricCurrent JuiceNet_CurrentLimit "Current Limit [%d %unit%]" { channel="juicenet:device:myaccount:JamesCharger:currentLimit" }
|
||||
Number:ElectricCurrent JuiceNet_Current "Current [%.1f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:current" }
|
||||
Number:ElectricPotential JuiceNet_Voltage "Voltage [%d %unit%]" { channel="juicenet:device:myaccount:JamesCharger:voltage" }
|
||||
Number:Energy JuiceNet_Energy "Current Energy [%.1f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:energy" }
|
||||
Number:Power JuiceNet_Power "Charging Power [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:power" }
|
||||
Number JuiceNet_Savings "Savings [$%.2f]" { channel="juicenet:device:myaccount:JamesCharger:savings" }
|
||||
Number:Time JuiceNet_ChargingTime "Charging Time [%.0f %unit%]" { channel="jjuicenet:device:myaccount:JamesCharger:chargingTime" }
|
||||
Number:Energy JuiceNet_EnergyToAdd "Energy to Add [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:energyToAdd" }
|
||||
Number:Energy JuiceNet_EnergyAtPlugin "Energy at Plugin [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:energyAtPlugin" }
|
||||
Number:Energy JuiceNet_LifetimeEnergy "Lifetime Energy [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:lifetimeEnergy" }
|
||||
Number JuiceNet_GasCost "Gas Cost [$%.2f]" { channel="juicenet:device:myaccount:JamesCharger:gasCost" }
|
||||
Number JuiceNet_FuelConsumption "Fuel consumption [%.1f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:fuelConsumption" }
|
||||
Number JuiceNet_Ecost "Utility Energy Cost [$%.2f]" { channel="juicenet:device:myaccount:JamesCharger:ecost" }
|
||||
Number JuiceNet_LifetimeSavings "Lifetime Savings [$%.2f]" { channel="juicenet:device:myaccount:JamesCharger:lifetimeSavings" }
|
||||
Number:Power JuiceNet_EnergyPerMile "Energy Hours Per Mile [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:energyPerMile" }
|
||||
String JuiceNet_CarDescription "Car Description" { channel="juicenet:device:myaccount:JamesCharger:carDescription" }
|
||||
Number:Length JuiceNet_CarBatteryRange "Mileage Range [%d %unit%]" { channel="juicenet:device:myaccount:JamesCharger:carBatteryRange" }
|
||||
Number:Energy JuiceNet_CarBatterySize "Car Battery Pack Size [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:carBatterySize" }
|
||||
Number:Power JuiceNet_CarChargineRage "Car Charging Rate [%.2f %unit%]" { channel="juicenet:device:myaccount:JamesCharger:carChargingRate" }
|
||||
|
||||
```
|
||||
|
||||
## Widget
|
||||
|
||||
The following custom widget can be used with this binding.
|
||||
|
||||
![JuiceBox Widget](doc/widget.png)
|
||||
|
||||
```
|
||||
uid: widget_JuiceBox
|
||||
tags: []
|
||||
props:
|
||||
parameters:
|
||||
- description: Prefix for the items with the data
|
||||
label: Item prefix
|
||||
name: prefix
|
||||
required: false
|
||||
type: TEXT
|
||||
parameterGroups: []
|
||||
timestamp: May 10, 2021, 2:38:55 PM
|
||||
component: f7-card
|
||||
config:
|
||||
title: =items[props.prefix + "_Name"].state
|
||||
style:
|
||||
border-radius: var(--f7-card-expandable-border-radius)
|
||||
--f7-card-header-border-color: none
|
||||
slots:
|
||||
default:
|
||||
- component: f7-card-content
|
||||
slots:
|
||||
default:
|
||||
- component: f7-row
|
||||
config:
|
||||
class:
|
||||
- display-flex
|
||||
- align-content-stretch
|
||||
- align-items-center
|
||||
slots:
|
||||
default:
|
||||
- component: f7-gauge
|
||||
config:
|
||||
type: semicircle
|
||||
size: 270
|
||||
value: =Number.parseFloat(items[props.prefix + "_CurrentEnergy"].state) / Number.parseFloat(items[props.prefix + "_CarBatteryPackSize"].state)
|
||||
bg-color: transparent
|
||||
border-bg-color: '=(items[props.prefix + "_DeviceState"].state === "charging") ? "#577543" : (items[props.prefix + "_DeviceState"].state === "plugged") ? "#8f6c2f" : "#595959"'
|
||||
border-color: '=(items[props.prefix + "_DeviceState"].state === "charging") ? "#90d164" : (items[props.prefix + "_DeviceState"].state === "plugged") ? "#ed9c11" : "#adadad"'
|
||||
borderWidth: 40
|
||||
value-text: =items[props.prefix + "_CurrentEnergy"].displayState
|
||||
value-text-color: '=(items[props.prefix + "_DeviceState"].state === "charging") ? "#90d164" : (items[props.prefix + "_DeviceState"].state === "plugged") ? "#ed9c11" : "#adadad"'
|
||||
value-font-size: 20
|
||||
value-font-weight: 500
|
||||
label-text: =items[props.prefix + "_DeviceState"].displayState
|
||||
label-text-color: white
|
||||
label-font-size: 18
|
||||
label-font-weight: 400
|
||||
noBorder: true
|
||||
outline: true
|
||||
- component: f7-row
|
||||
config:
|
||||
class:
|
||||
- display-flex
|
||||
- justify-content-center
|
||||
- align-content-stretch
|
||||
- align-items-center
|
||||
- margin-left
|
||||
slots:
|
||||
default:
|
||||
- component: f7-segmented
|
||||
config:
|
||||
strong: true
|
||||
style:
|
||||
width: 80%
|
||||
slots:
|
||||
default:
|
||||
- component: oh-button
|
||||
config:
|
||||
text: Start
|
||||
color: blue
|
||||
size: 24
|
||||
active: =(items[props.prefix + "_ChargingState"].state === "start")
|
||||
action: command
|
||||
actionItem: =props.prefix + "_ChargingState"
|
||||
actionCommand: start
|
||||
- component: oh-button
|
||||
config:
|
||||
text: Smart
|
||||
color: blue
|
||||
size: 24
|
||||
active: =(items[props.prefix + "_ChargingState"].state === 'smart')
|
||||
action: command
|
||||
actionItem: =props.prefix + "_ChargingState"
|
||||
actionCommand: smart
|
||||
- component: oh-button
|
||||
config:
|
||||
text: Stop
|
||||
color: blue
|
||||
size: 24
|
||||
active: =(items[props.prefix + "_ChargingState"].state === "stop")
|
||||
action: command
|
||||
actionItem: =props.prefix + "_ChargingState"
|
||||
actionCommand: stop
|
||||
- component: f7-row
|
||||
config:
|
||||
class:
|
||||
- display-flex
|
||||
- justify-content-space-evenly
|
||||
- align-content-stretch
|
||||
- align-items-center
|
||||
- height: 40px
|
||||
style:
|
||||
--f7-chip-font-size: 14px
|
||||
--f7-chip-height: 28px
|
||||
padding-top: 12px
|
||||
slots:
|
||||
default:
|
||||
- component: f7-chip
|
||||
config:
|
||||
visible: =(items[props.prefix + "_DeviceState"].state === "charging")
|
||||
text: '="Power: " + items[props.prefix + "_ChargingPower"].state'
|
||||
iconF7: bolt_fill
|
||||
media-bg-color: blue
|
||||
bg-color: gray
|
||||
label: hello
|
||||
style:
|
||||
padding-rightc: 12px
|
||||
- component: f7-chip
|
||||
config:
|
||||
visible: =(items[props.prefix + "_DeviceState"].state === "charging")
|
||||
text: '="Current: " + items[props.prefix + "_Current"].state'
|
||||
iconF7: arrow_up_circl
|
||||
media-bg-color: blue
|
||||
bg-color: gray
|
||||
- component: f7-chip
|
||||
config:
|
||||
text: '="Voltage: " + items[props.prefix + "_Voltage"].state'
|
||||
iconF7: plusminus
|
||||
media-bg-color: blue
|
||||
bg-color: gray
|
||||
- component: f7-chip
|
||||
config:
|
||||
visible: =(items[props.prefix + "_ChargingState"].state === 'smart')
|
||||
text: '="Charge at: " + items[props.prefix + "_TargetTime"].displayState'
|
||||
iconF7: clock
|
||||
media-bg-color: blue
|
||||
bg-color: gray
|
||||
- component: f7-chip
|
||||
config:
|
||||
visible: =(items[props.prefix + "_DeviceState"].state === 'charging')
|
||||
text: '="Charge Time Left: " + items[props.prefix + "_ChargingTimeLeft"].displayState'
|
||||
iconF7: timer
|
||||
media-bg-color: blue
|
||||
bg-color: gray
|
||||
- component: f7-card-footer
|
||||
slots:
|
||||
default:
|
||||
- component: Label
|
||||
config:
|
||||
text: =items[props.prefix + "_CarDescription"].state
|
||||
```
|
||||
|
BIN
bundles/org.openhab.binding.juicenet/doc/widget.png
Normal file
BIN
bundles/org.openhab.binding.juicenet/doc/widget.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
16
bundles/org.openhab.binding.juicenet/pom.xml
Normal file
16
bundles/org.openhab.binding.juicenet/pom.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.4.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.juicenet</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: JuiceNet Binding</name>
|
||||
</project>
|
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.juicenet-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-juicenet" description="JuiceNet Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.juicenet/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetBindingConstants {
|
||||
private static final String BINDING_ID = "juicenet";
|
||||
|
||||
// List of Bridge Type
|
||||
public static final String BRIDGE = "account";
|
||||
|
||||
// List of all Device Types
|
||||
public static final String DEVICE = "device";
|
||||
|
||||
// List of all Bridge Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE);
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID DEVICE_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE);
|
||||
|
||||
// Device config parameter
|
||||
public static final String PARAMETER_UNIT_ID = "unitID";
|
||||
|
||||
// Device properties
|
||||
public static final String PROPERTY_NAME = "name";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_NAME = "name";
|
||||
public static final String CHANNEL_CHARGING_STATE = "chargingState";
|
||||
public static final String CHANNEL_STATE = "state";
|
||||
public static final String CHANNEL_MESSAGE = "message";
|
||||
public static final String CHANNEL_OVERRIDE = "override";
|
||||
public static final String CHANNEL_CHARGING_TIME_LEFT = "chargingTimeLeft";
|
||||
public static final String CHANNEL_PLUG_UNPLUG_TIME = "plugUnplugTime";
|
||||
public static final String CHANNEL_TARGET_TIME = "targetTime";
|
||||
public static final String CHANNEL_UNIT_TIME = "unitTime";
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_CURRENT_LIMIT = "currentLimit";
|
||||
public static final String CHANNEL_CURRENT = "current";
|
||||
public static final String CHANNEL_VOLTAGE = "voltage";
|
||||
public static final String CHANNEL_ENERGY = "energy";
|
||||
public static final String CHANNEL_SAVINGS = "savings";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_CHARGING_TIME = "chargingTime";
|
||||
public static final String CHANNEL_ENERGY_AT_PLUGIN = "energyAtPlugin";
|
||||
public static final String CHANNEL_ENERGY_TO_ADD = "energyToAdd";
|
||||
public static final String CHANNEL_LIFETIME_ENERGY = "lifetimeEnergy";
|
||||
public static final String CHANNEL_LIFETIME_SAVINGS = "lifetimeSavings";
|
||||
|
||||
public static final String CHANNEL_GAS_COST = "gasCost";
|
||||
public static final String CHANNEL_FUEL_CONSUMPTION = "fuelConsumption";
|
||||
public static final String CHANNEL_ECOST = "ecost";
|
||||
public static final String CHANNEL_ENERGY_PER_MILE = "energyPerMile";
|
||||
|
||||
public static final String CHANNEL_CAR_DESCRIPTION = "carDescription";
|
||||
public static final String CHANNEL_CAR_BATTERY_SIZE = "carBatterySize";
|
||||
public static final String CHANNEL_CAR_BATTERY_RANGE = "carBatteryRange";
|
||||
public static final String CHANNEL_CAR_CHARGING_RATE = "carChargingRate";
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal;
|
||||
|
||||
import static org.openhab.binding.juicenet.internal.JuiceNetBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.juicenet.internal.handler.JuiceNetBridgeHandler;
|
||||
import org.openhab.binding.juicenet.internal.handler.JuiceNetDeviceHandler;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.juicenet", service = ThingHandlerFactory.class)
|
||||
public class JuiceNetHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_THING_TYPE, DEVICE_THING_TYPE);
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Activate
|
||||
public JuiceNetHandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(BRIDGE_THING_TYPE)) {
|
||||
return new JuiceNetBridgeHandler((Bridge) thing, httpClientFactory.getCommonHttpClient());
|
||||
} else if (thingTypeUID.equals(DEVICE_THING_TYPE)) {
|
||||
return new JuiceNetDeviceHandler(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiDevice;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiDeviceStatus;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiInfo;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiTouSchedule;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetApi} is responsible for implementing the api interface to the JuiceNet cloud server
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApi {
|
||||
private final Logger logger = LoggerFactory.getLogger(JuiceNetApi.class);
|
||||
|
||||
private static final String API_HOST = "https://jbv1-api.emotorwerks.com/";
|
||||
private static final String API_ACCOUNT = API_HOST + "box_pin";
|
||||
private static final String API_DEVICE = API_HOST + "box_api_secure";
|
||||
|
||||
private String apiToken = "";
|
||||
private HttpClient httpClient;
|
||||
private ThingUID bridgeUID;
|
||||
|
||||
public enum ApiCommand {
|
||||
GET_ACCOUNT_UNITS("get_account_units", API_ACCOUNT),
|
||||
GET_STATE("get_state", API_DEVICE),
|
||||
SET_CHARGING_LIMIT("set_limit", API_DEVICE),
|
||||
GET_SCHEDULE("get_schedule", API_DEVICE),
|
||||
SET_SCHEDULE("set_schedule", API_DEVICE),
|
||||
GET_INFO("get_info", API_DEVICE),
|
||||
SET_OVERRIDE("set_override", API_DEVICE);
|
||||
|
||||
final String command;
|
||||
final String uri;
|
||||
|
||||
ApiCommand(String command, String uri) {
|
||||
this.command = command;
|
||||
this.uri = uri;
|
||||
}
|
||||
}
|
||||
|
||||
public JuiceNetApi(HttpClient httpClient, ThingUID bridgeUID) {
|
||||
this.bridgeUID = bridgeUID;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void setApiToken(String apiToken) {
|
||||
this.apiToken = apiToken;
|
||||
}
|
||||
|
||||
public List<JuiceNetApiDevice> queryDeviceList() throws JuiceNetApiException, InterruptedException {
|
||||
JuiceNetApiDevice[] listDevices;
|
||||
try {
|
||||
JsonObject jsonResponse = postApiCommand(ApiCommand.GET_ACCOUNT_UNITS, null);
|
||||
|
||||
JsonElement unitsElement = jsonResponse.get("units");
|
||||
if (unitsElement == null) {
|
||||
throw new JuiceNetApiException("getDevices from Juicenet API failed, no 'units' element in response.");
|
||||
}
|
||||
|
||||
listDevices = new Gson().fromJson(unitsElement.getAsJsonArray(), JuiceNetApiDevice[].class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JuiceNetApiException("getDevices from JuiceNet API failed, invalid JSON list.");
|
||||
} catch (IllegalStateException e) {
|
||||
throw new JuiceNetApiException("getDevices from JuiceNet API failed - did not return valid array.");
|
||||
}
|
||||
|
||||
return Arrays.asList(listDevices);
|
||||
}
|
||||
|
||||
public JuiceNetApiDeviceStatus queryDeviceStatus(String token) throws JuiceNetApiException, InterruptedException {
|
||||
JuiceNetApiDeviceStatus deviceStatus;
|
||||
try {
|
||||
JsonObject jsonResponse = postApiCommand(ApiCommand.GET_STATE, token);
|
||||
|
||||
deviceStatus = new Gson().fromJson(jsonResponse, JuiceNetApiDeviceStatus.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JuiceNetApiException("queryDeviceStatus from JuiceNet API failed, invalid JSON list.");
|
||||
} catch (IllegalStateException e) {
|
||||
throw new JuiceNetApiException("queryDeviceStatus from JuiceNet API failed - did not return valid array.");
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(deviceStatus);
|
||||
}
|
||||
|
||||
public JuiceNetApiInfo queryInfo(String token) throws InterruptedException, JuiceNetApiException {
|
||||
JuiceNetApiInfo info;
|
||||
try {
|
||||
JsonObject jsonResponse = postApiCommand(ApiCommand.GET_INFO, token);
|
||||
|
||||
info = new Gson().fromJson(jsonResponse, JuiceNetApiInfo.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JuiceNetApiException("queryInfo from JuiceNet API failed, invalid JSON list.");
|
||||
} catch (IllegalStateException e) {
|
||||
throw new JuiceNetApiException("queryInfo from JuiceNet API failed - did not return valid array.");
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(info);
|
||||
}
|
||||
|
||||
public JuiceNetApiTouSchedule queryTOUSchedule(String token) throws InterruptedException, JuiceNetApiException {
|
||||
JuiceNetApiTouSchedule deviceTouSchedule;
|
||||
try {
|
||||
JsonObject jsonResponse = postApiCommand(ApiCommand.GET_SCHEDULE, token);
|
||||
|
||||
deviceTouSchedule = new Gson().fromJson(jsonResponse, JuiceNetApiTouSchedule.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new JuiceNetApiException("queryTOUSchedule from JuiceNet API failed, invalid JSON list.");
|
||||
} catch (IllegalStateException e) {
|
||||
throw new JuiceNetApiException("queryTOUSchedule from JuiceNet API failed - did not return valid array.");
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(deviceTouSchedule);
|
||||
}
|
||||
|
||||
public void setOverride(String token, int energy_at_plugin, Long override_time, int energy_to_add)
|
||||
throws InterruptedException, JuiceNetApiException {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
||||
params.put("energy_at_plugin", Integer.toString(energy_at_plugin));
|
||||
params.put("override_time", Long.toString(energy_at_plugin));
|
||||
params.put("energy_to_add", Integer.toString(energy_at_plugin));
|
||||
|
||||
postApiCommand(ApiCommand.SET_OVERRIDE, token, params);
|
||||
}
|
||||
|
||||
public void setCurrentLimit(String token, int limit) throws InterruptedException, JuiceNetApiException {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
||||
params.put("amperage", Integer.toString(limit));
|
||||
|
||||
postApiCommand(ApiCommand.SET_OVERRIDE, token, params);
|
||||
}
|
||||
|
||||
public JsonObject postApiCommand(ApiCommand cmd, @Nullable String token)
|
||||
throws InterruptedException, JuiceNetApiException {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
||||
return postApiCommand(cmd, token, params);
|
||||
}
|
||||
|
||||
public JsonObject postApiCommand(ApiCommand cmd, @Nullable String token, Map<String, Object> params)
|
||||
throws InterruptedException, JuiceNetApiException {
|
||||
Request request = httpClient.POST(cmd.uri);
|
||||
request.header(HttpHeader.CONTENT_TYPE, "application/json");
|
||||
|
||||
// Add required params
|
||||
params.put("cmd", cmd.command);
|
||||
params.put("device_id", bridgeUID.getAsString());
|
||||
params.put("account_token", apiToken);
|
||||
|
||||
if (token != null) {
|
||||
params.put("token", token);
|
||||
}
|
||||
|
||||
JsonObject jsonResponse;
|
||||
try {
|
||||
request.content(new StringContentProvider(new Gson().toJson(params)), "application/json");
|
||||
ContentResponse response = request.send();
|
||||
if (response.getStatus() != HttpStatus.OK_200) {
|
||||
throw new JuiceNetApiException(
|
||||
cmd.command + "from JuiceNet API unsucessful, please check configuation. (HTTP code :"
|
||||
+ response.getStatus() + ").");
|
||||
}
|
||||
|
||||
String responseString = response.getContentAsString();
|
||||
logger.trace("{}", responseString);
|
||||
|
||||
jsonResponse = JsonParser.parseString(responseString).getAsJsonObject();
|
||||
JsonElement successElement = jsonResponse.get("success");
|
||||
if (successElement == null) {
|
||||
throw new JuiceNetApiException(
|
||||
cmd.command + " from JuiceNet API failed, 'success' element missing from response.");
|
||||
}
|
||||
boolean success = successElement.getAsBoolean();
|
||||
|
||||
if (!success) {
|
||||
throw new JuiceNetApiException(cmd.command + " from JuiceNet API failed, please check configuration.");
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
throw new JuiceNetApiException(cmd.command + " from JuiceNet API failed, invalid JSON.");
|
||||
} catch (TimeoutException e) {
|
||||
throw new JuiceNetApiException(cmd.command + " from JuiceNet API timeout.");
|
||||
} catch (ExecutionException e) {
|
||||
throw new JuiceNetApiException(cmd.command + " from JuiceNet API execution issue.");
|
||||
}
|
||||
|
||||
return jsonResponse;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiException} implements an API Exception
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiException extends Exception {
|
||||
private static final long serialVersionUID = 5421236828224242152L;
|
||||
|
||||
public JuiceNetApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiCar } implements DTO for Car API call
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiCar {
|
||||
@SerializedName("car_id")
|
||||
public int carId;
|
||||
public String description = "";
|
||||
@SerializedName("battery_size_wh")
|
||||
public int batterySizeWH;
|
||||
@SerializedName("battery_range_m")
|
||||
public int batteryRangeM;
|
||||
@SerializedName("charging_rate_w")
|
||||
public int chargingRateW;
|
||||
@SerializedName("model_id")
|
||||
public String modelId = "";
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiDevice } implements DTO for Device Info API call
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiDevice {
|
||||
public String name = "";
|
||||
public String token = "";
|
||||
@SerializedName("unit_id")
|
||||
public String unitId = "";
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetDeviceChargingStatus } implements DTO for device charging status
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiDeviceChargingStatus {
|
||||
@SerializedName("amps_limit")
|
||||
public int ampsLimit;
|
||||
@SerializedName("amps_current")
|
||||
public float ampsCurrent;
|
||||
public int voltage;
|
||||
@SerializedName("wh_energy")
|
||||
public int whEnergy;
|
||||
public int savings;
|
||||
@SerializedName("watt_power")
|
||||
public int wattPower;
|
||||
@SerializedName("seconds_charging")
|
||||
public int secondsCharging;
|
||||
@SerializedName("wh_energy_at_plugin")
|
||||
public int whEnergyAtPlugin;
|
||||
@SerializedName("wh_energy_to_add")
|
||||
public int whEnergyToAdd;
|
||||
public int flags;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiDeviceLifetimeStatus } implements DTO for Device Lifetime Status
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiDeviceLifetimeStatus {
|
||||
@SerializedName("wh_energy")
|
||||
public int whEnergy;
|
||||
public int savings;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiDeviceStatus } implements DTO for Device Status
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiDeviceStatus {
|
||||
@SerializedName("ID")
|
||||
public String id = "";
|
||||
@SerializedName("info_timestamp")
|
||||
public Long infoTimestamp = (long) 0;
|
||||
@SerializedName("show_override")
|
||||
public boolean showOverride;
|
||||
public String state = "";
|
||||
public JuiceNetApiDeviceChargingStatus charging = new JuiceNetApiDeviceChargingStatus();
|
||||
public JuiceNetApiDeviceLifetimeStatus lifetime = new JuiceNetApiDeviceLifetimeStatus();
|
||||
@SerializedName("charging_time_left")
|
||||
public int chargingTimeLeft;
|
||||
@SerializedName("plug_unplug_time")
|
||||
public Long plugUnplugTime = (long) 0;
|
||||
@SerializedName("target_time")
|
||||
public Long targetTime = (long) 0;
|
||||
@SerializedName("unit_time")
|
||||
public Long unitTime = (long) 0;
|
||||
@SerializedName("utc_time")
|
||||
public Long utcTime = (long) 0;
|
||||
@SerializedName("default_target_time")
|
||||
public long defaultTargetTime = 0;
|
||||
@SerializedName("car_id")
|
||||
public int carId;
|
||||
public int temperature;
|
||||
public String message = "";
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiInfo } implements DTO for Info
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiInfo {
|
||||
public String name = "";
|
||||
public String address = "";
|
||||
public String city = "";
|
||||
public String zip = "";
|
||||
@SerializedName("country_code")
|
||||
public String countryCode = "";
|
||||
public String ip = "";
|
||||
@SerializedName("gascost")
|
||||
public int gasCost;
|
||||
public int mpg;
|
||||
public int ecost;
|
||||
@SerializedName("whpermile")
|
||||
public int whPerMile;
|
||||
public String timeZoneId = "";
|
||||
@SerializedName("amps_wire_rating")
|
||||
public int ampsWireRating;
|
||||
@SerializedName("amps_unit_rating")
|
||||
public int ampsUnitRating;
|
||||
public JuiceNetApiCar[] cars = {};
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiTouDay } implements DTO for TOU settings
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiTouDay {
|
||||
public int start;
|
||||
public int end;
|
||||
@SerializedName("car_ready_by")
|
||||
public int carReadyBy;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.api.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link JuiceNetApiTouSchedule } implements DTO for TOU schedule
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetApiTouSchedule {
|
||||
public String type = "";
|
||||
public JuiceNetApiTouDay weekday = new JuiceNetApiTouDay();
|
||||
public JuiceNetApiTouDay weenend = new JuiceNetApiTouDay();
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetBridgeConfiguration {
|
||||
public String apiToken = "";
|
||||
public int refreshInterval = 60;
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.juicenet.internal.JuiceNetBindingConstants.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.juicenet.internal.handler.JuiceNetBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetDiscoveryService} discovers all devices/zones reported by the FlumeTech Cloud. This requires the
|
||||
* api
|
||||
* key to get access to the cloud data.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService {
|
||||
private final Logger logger = LoggerFactory.getLogger(JuiceNetDiscoveryService.class);
|
||||
|
||||
private @Nullable JuiceNetBridgeHandler bridgeHandler;
|
||||
|
||||
private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Set.of(DEVICE_THING_TYPE);
|
||||
|
||||
public JuiceNetDiscoveryService() {
|
||||
super(DISCOVERABLE_THING_TYPES_UIDS, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
super.activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void startScan() {
|
||||
Objects.requireNonNull(bridgeHandler).iterateApiDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof JuiceNetBridgeHandler) {
|
||||
JuiceNetBridgeHandler bridgeHandler = (JuiceNetBridgeHandler) handler;
|
||||
bridgeHandler.setDiscoveryService(this);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
} else {
|
||||
this.bridgeHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.bridgeHandler;
|
||||
}
|
||||
|
||||
public void notifyDiscoveryDevice(String id, String name) {
|
||||
JuiceNetBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
Objects.requireNonNull(bridgeHandler, "Discovery with null bridgehandler.");
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
|
||||
ThingUID uid = new ThingUID(DEVICE_THING_TYPE, bridgeUID, id);
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
|
||||
.withProperty(PARAMETER_UNIT_ID, id).withLabel(name).build();
|
||||
thingDiscovered(result);
|
||||
logger.debug("Discovered JuiceNetDevice {} - {}", uid, name);
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.handler;
|
||||
|
||||
import static org.openhab.binding.juicenet.internal.JuiceNetBindingConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.juicenet.internal.api.JuiceNetApi;
|
||||
import org.openhab.binding.juicenet.internal.api.JuiceNetApiException;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiDevice;
|
||||
import org.openhab.binding.juicenet.internal.config.JuiceNetBridgeConfiguration;
|
||||
import org.openhab.binding.juicenet.internal.discovery.JuiceNetDiscoveryService;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(JuiceNetBridgeHandler.class);
|
||||
|
||||
private JuiceNetBridgeConfiguration config = new JuiceNetBridgeConfiguration();
|
||||
private final JuiceNetApi api;
|
||||
|
||||
public JuiceNetApi getApi() {
|
||||
return api;
|
||||
}
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable JuiceNetDiscoveryService discoveryService;
|
||||
|
||||
public JuiceNetBridgeHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
|
||||
this.api = new JuiceNetApi(httpClient, getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(JuiceNetBridgeConfiguration.class);
|
||||
|
||||
logger.trace("Bridge initialized: {}", Objects.requireNonNull(getThing()).getUID());
|
||||
|
||||
api.setApiToken(config.apiToken);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
// Bridge will go online after the first successful API call in iterateApiDevices. iterateApiDevices will be
|
||||
// called when a child device attempts to goOnline and needs to retrieve the api token
|
||||
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::pollDevices, 10, config.refreshInterval, TimeUnit.SECONDS);
|
||||
|
||||
// Call here in order to discover any devices.
|
||||
iterateApiDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Handler disposed.");
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDiscoveryService(JuiceNetDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
// Call here to set the Api Token for any newly initialized Child devices
|
||||
iterateApiDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services registered for this bridge. Provides the discovery service.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(JuiceNetDiscoveryService.class);
|
||||
}
|
||||
|
||||
public void handleApiException(Exception e) {
|
||||
if (e instanceof JuiceNetApiException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.toString());
|
||||
} else if (e instanceof InterruptedException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.toString());
|
||||
Thread.currentThread().interrupt();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Thing getThingById(String id) {
|
||||
List<Thing> childThings = getThing().getThings();
|
||||
|
||||
for (Thing childThing : childThings) {
|
||||
Configuration configuration = childThing.getConfiguration();
|
||||
|
||||
String childId = configuration.get(PARAMETER_UNIT_ID).toString();
|
||||
|
||||
if (childId.equals(id)) {
|
||||
return childThing;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// This function will query the list of devices from the API and then set the name/token in the child handlers. If a
|
||||
// child does not exist, it will notify the Discovery service. If it is successful, it will ensure the bridge status
|
||||
// is updated
|
||||
// to ONLINE.
|
||||
public void iterateApiDevices() {
|
||||
List<JuiceNetApiDevice> listDevices;
|
||||
|
||||
try {
|
||||
listDevices = api.queryDeviceList();
|
||||
} catch (JuiceNetApiException | InterruptedException e) {
|
||||
handleApiException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
JuiceNetDiscoveryService discoveryService = this.discoveryService;
|
||||
for (JuiceNetApiDevice dev : listDevices) {
|
||||
Thing childThing = getThingById(dev.unitId);
|
||||
if (childThing == null) {
|
||||
if (discoveryService != null) {
|
||||
discoveryService.notifyDiscoveryDevice(dev.unitId, dev.name);
|
||||
}
|
||||
} else {
|
||||
JuiceNetDeviceHandler childHandler = (JuiceNetDeviceHandler) childThing.getHandler();
|
||||
if (childHandler != null) {
|
||||
childHandler.setNameAndToken(dev.name, dev.token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pollDevices() {
|
||||
List<Thing> things = getThing().getThings();
|
||||
|
||||
for (Thing t : things) {
|
||||
if (!t.getThingTypeUID().equals(DEVICE_THING_TYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JuiceNetDeviceHandler handler = (JuiceNetDeviceHandler) t.getHandler();
|
||||
if (handler == null) {
|
||||
logger.trace("no handler for thing: {}", t.getUID());
|
||||
continue;
|
||||
}
|
||||
|
||||
handler.queryDeviceStatusAndInfo();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,360 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.juicenet.internal.handler;
|
||||
|
||||
import static org.openhab.binding.juicenet.internal.JuiceNetBindingConstants.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.juicenet.internal.api.JuiceNetApi;
|
||||
import org.openhab.binding.juicenet.internal.api.JuiceNetApiException;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiCar;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiDeviceStatus;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiInfo;
|
||||
import org.openhab.binding.juicenet.internal.api.dto.JuiceNetApiTouSchedule;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link JuiceNetDeviceHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jeff James - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JuiceNetDeviceHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(JuiceNetDeviceHandler.class);
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
// properties
|
||||
private String name = "";
|
||||
|
||||
private String token = "";
|
||||
private long targetTimeTou = 0;
|
||||
private long lastInfoTimestamp = 0;
|
||||
|
||||
JuiceNetApiDeviceStatus deviceStatus = new JuiceNetApiDeviceStatus();
|
||||
JuiceNetApiInfo deviceInfo = new JuiceNetApiInfo();
|
||||
JuiceNetApiTouSchedule deviceTouSchedule = new JuiceNetApiTouSchedule();
|
||||
JuiceNetApiCar deviceCar = new JuiceNetApiCar();
|
||||
|
||||
public JuiceNetDeviceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
public void setNameAndToken(String name, String token) {
|
||||
logger.trace("setNameAndToken");
|
||||
this.token = token;
|
||||
|
||||
if (!name.equals(this.name)) {
|
||||
updateProperty(PROPERTY_NAME, name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
goOnline();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.trace("Device initialized: {}", Objects.requireNonNull(getThing().getUID()));
|
||||
Configuration configuration = getThing().getConfiguration();
|
||||
|
||||
String stringId = configuration.get(PARAMETER_UNIT_ID).toString();
|
||||
if (stringId.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.id-missing");
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// This device will go ONLINE on the first successful API call in queryDeviceStatusAndInfo
|
||||
}
|
||||
|
||||
private void handleApiException(Exception e) {
|
||||
if (e instanceof JuiceNetApiException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.toString());
|
||||
} else if (e instanceof InterruptedException) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.toString());
|
||||
Thread.currentThread().interrupt();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void goOnline() {
|
||||
logger.trace("goOnline");
|
||||
if (this.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.non-existent-device");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
tryQueryDeviceStatusAndInfo();
|
||||
} catch (JuiceNetApiException | InterruptedException e) {
|
||||
handleApiException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JuiceNetApi getApi() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.configuration-error.bridge-missing");
|
||||
return null;
|
||||
}
|
||||
BridgeHandler handler = Objects.requireNonNull(bridge.getHandler());
|
||||
|
||||
return ((JuiceNetBridgeHandler) handler).getApi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
JuiceNetApi api = getApi();
|
||||
if (api == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_NAME:
|
||||
case CHANNEL_STATE:
|
||||
case CHANNEL_MESSAGE:
|
||||
case CHANNEL_OVERRIDE:
|
||||
case CHANNEL_CHARGING_TIME_LEFT:
|
||||
case CHANNEL_PLUG_UNPLUG_TIME:
|
||||
case CHANNEL_TARGET_TIME:
|
||||
case CHANNEL_UNIT_TIME:
|
||||
case CHANNEL_TEMPERATURE:
|
||||
case CHANNEL_CURRENT_LIMIT:
|
||||
case CHANNEL_CURRENT:
|
||||
case CHANNEL_VOLTAGE:
|
||||
case CHANNEL_ENERGY:
|
||||
case CHANNEL_SAVINGS:
|
||||
case CHANNEL_POWER:
|
||||
case CHANNEL_CHARGING_TIME:
|
||||
case CHANNEL_ENERGY_AT_PLUGIN:
|
||||
case CHANNEL_ENERGY_TO_ADD:
|
||||
case CHANNEL_LIFETIME_ENERGY:
|
||||
case CHANNEL_LIFETIME_SAVINGS:
|
||||
case CHANNEL_CAR_DESCRIPTION:
|
||||
case CHANNEL_CAR_BATTERY_SIZE:
|
||||
case CHANNEL_CAR_BATTERY_RANGE:
|
||||
case CHANNEL_CAR_CHARGING_RATE:
|
||||
refreshStatusChannels();
|
||||
break;
|
||||
case CHANNEL_GAS_COST:
|
||||
case CHANNEL_FUEL_CONSUMPTION:
|
||||
case CHANNEL_ECOST:
|
||||
case CHANNEL_ENERGY_PER_MILE:
|
||||
refreshInfoChannels();
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_CURRENT_LIMIT:
|
||||
int limit = ((QuantityType<?>) command).intValue();
|
||||
api.setCurrentLimit(Objects.requireNonNull(token), limit);
|
||||
break;
|
||||
case CHANNEL_TARGET_TIME: {
|
||||
int energyAtPlugin = 0;
|
||||
int energyToAdd = deviceCar.batterySizeWH;
|
||||
|
||||
if (!(command instanceof DateTimeType)) {
|
||||
logger.info("Target Time is not an instance of DateTimeType");
|
||||
return;
|
||||
}
|
||||
|
||||
ZonedDateTime datetime = ((DateTimeType) command).getZonedDateTime();
|
||||
Long targetTime = datetime.toEpochSecond() + datetime.get(ChronoField.OFFSET_SECONDS);
|
||||
logger.debug("DateTime: {} - {}", datetime.toString(), targetTime);
|
||||
|
||||
api.setOverride(Objects.requireNonNull(token), energyAtPlugin, targetTime, energyToAdd);
|
||||
|
||||
break;
|
||||
}
|
||||
case CHANNEL_CHARGING_STATE: {
|
||||
String state = ((StringType) command).toString();
|
||||
Long overrideTime = deviceStatus.unitTime;
|
||||
int energyAtPlugin = 0;
|
||||
int energyToAdd = deviceCar.batterySizeWH;
|
||||
|
||||
switch (state) {
|
||||
case "stop":
|
||||
if (targetTimeTou == 0) {
|
||||
targetTimeTou = deviceStatus.targetTime;
|
||||
}
|
||||
overrideTime = deviceStatus.unitTime + 31556926;
|
||||
break;
|
||||
case "start":
|
||||
if (targetTimeTou == 0) {
|
||||
targetTimeTou = deviceStatus.targetTime;
|
||||
}
|
||||
overrideTime = deviceStatus.unitTime;
|
||||
break;
|
||||
case "smart":
|
||||
overrideTime = deviceStatus.defaultTargetTime;
|
||||
break;
|
||||
}
|
||||
|
||||
api.setOverride(Objects.requireNonNull(token), energyAtPlugin, overrideTime, energyToAdd);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (JuiceNetApiException | InterruptedException e) {
|
||||
handleApiException(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryQueryDeviceStatusAndInfo() throws JuiceNetApiException, InterruptedException {
|
||||
String apiToken = Objects.requireNonNull(this.token);
|
||||
JuiceNetApi api = getApi();
|
||||
if (api == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
deviceStatus = api.queryDeviceStatus(apiToken);
|
||||
|
||||
if (deviceStatus.infoTimestamp > lastInfoTimestamp) {
|
||||
lastInfoTimestamp = deviceStatus.infoTimestamp;
|
||||
|
||||
deviceInfo = api.queryInfo(apiToken);
|
||||
deviceTouSchedule = api.queryTOUSchedule(apiToken);
|
||||
refreshInfoChannels();
|
||||
}
|
||||
|
||||
int carId = deviceStatus.carId;
|
||||
for (JuiceNetApiCar car : deviceInfo.cars) {
|
||||
if (car.carId == carId) {
|
||||
this.deviceCar = car;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
refreshStatusChannels();
|
||||
}
|
||||
|
||||
public void queryDeviceStatusAndInfo() {
|
||||
logger.trace("queryStatusAndInfo");
|
||||
ThingStatus status = getThing().getStatus();
|
||||
|
||||
if (status != ThingStatus.ONLINE) {
|
||||
goOnline();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
tryQueryDeviceStatusAndInfo();
|
||||
} catch (JuiceNetApiException | InterruptedException e) {
|
||||
handleApiException(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private ZonedDateTime toZonedDateTime(long localEpochSeconds) {
|
||||
return Instant.ofEpochSecond(localEpochSeconds).atZone(timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
private void refreshStatusChannels() {
|
||||
updateState(CHANNEL_STATE, new StringType(deviceStatus.state));
|
||||
|
||||
if (deviceStatus.targetTime <= deviceStatus.unitTime) {
|
||||
updateState(CHANNEL_CHARGING_STATE, new StringType("start"));
|
||||
} else if ((deviceStatus.targetTime - deviceStatus.unitTime) < TimeUnit.DAYS.toSeconds(2)) {
|
||||
updateState(CHANNEL_CHARGING_STATE, new StringType("smart"));
|
||||
} else {
|
||||
updateState(CHANNEL_CHARGING_STATE, new StringType("stop"));
|
||||
}
|
||||
|
||||
updateState(CHANNEL_MESSAGE, new StringType(deviceStatus.message));
|
||||
updateState(CHANNEL_OVERRIDE, OnOffType.from(deviceStatus.showOverride));
|
||||
updateState(CHANNEL_CHARGING_TIME_LEFT, new QuantityType<>(deviceStatus.chargingTimeLeft, Units.SECOND));
|
||||
updateState(CHANNEL_PLUG_UNPLUG_TIME, new DateTimeType(toZonedDateTime(deviceStatus.plugUnplugTime)));
|
||||
updateState(CHANNEL_TARGET_TIME, new DateTimeType(toZonedDateTime(deviceStatus.targetTime)));
|
||||
updateState(CHANNEL_UNIT_TIME, new DateTimeType(toZonedDateTime(deviceStatus.utcTime)));
|
||||
updateState(CHANNEL_TEMPERATURE, new QuantityType<>(deviceStatus.temperature, SIUnits.CELSIUS));
|
||||
updateState(CHANNEL_CURRENT_LIMIT, new QuantityType<>(deviceStatus.charging.ampsLimit, Units.AMPERE));
|
||||
updateState(CHANNEL_CURRENT, new QuantityType<>(deviceStatus.charging.ampsCurrent, Units.AMPERE));
|
||||
updateState(CHANNEL_VOLTAGE, new QuantityType<>(deviceStatus.charging.voltage, Units.VOLT));
|
||||
updateState(CHANNEL_ENERGY, new QuantityType<>(deviceStatus.charging.whEnergy, Units.WATT_HOUR));
|
||||
updateState(CHANNEL_SAVINGS, new DecimalType(deviceStatus.charging.savings / 100.0));
|
||||
updateState(CHANNEL_POWER, new QuantityType<>(deviceStatus.charging.wattPower, Units.WATT));
|
||||
updateState(CHANNEL_CHARGING_TIME, new QuantityType<>(deviceStatus.charging.secondsCharging, Units.SECOND));
|
||||
updateState(CHANNEL_ENERGY_AT_PLUGIN,
|
||||
new QuantityType<>(deviceStatus.charging.whEnergyAtPlugin, Units.WATT_HOUR));
|
||||
updateState(CHANNEL_ENERGY_TO_ADD, new QuantityType<>(deviceStatus.charging.whEnergyToAdd, Units.WATT_HOUR));
|
||||
updateState(CHANNEL_LIFETIME_ENERGY, new QuantityType<>(deviceStatus.lifetime.whEnergy, Units.WATT_HOUR));
|
||||
updateState(CHANNEL_LIFETIME_SAVINGS, new DecimalType(deviceStatus.lifetime.savings / 100.0));
|
||||
|
||||
// update Car items
|
||||
updateState(CHANNEL_CAR_DESCRIPTION, new StringType(deviceCar.description));
|
||||
updateState(CHANNEL_CAR_BATTERY_SIZE, new QuantityType<>(deviceCar.batterySizeWH, Units.WATT_HOUR));
|
||||
updateState(CHANNEL_CAR_BATTERY_RANGE, new QuantityType<>(deviceCar.batteryRangeM, ImperialUnits.MILE));
|
||||
updateState(CHANNEL_CAR_CHARGING_RATE, new QuantityType<>(deviceCar.chargingRateW, Units.WATT));
|
||||
}
|
||||
|
||||
private void refreshInfoChannels() {
|
||||
updateState(CHANNEL_NAME, new StringType(name));
|
||||
updateState(CHANNEL_GAS_COST, new DecimalType(deviceInfo.gasCost / 100.0));
|
||||
// currently there is no unit defined for fuel consumption
|
||||
updateState(CHANNEL_FUEL_CONSUMPTION, new DecimalType(deviceInfo.mpg));
|
||||
updateState(CHANNEL_ECOST, new DecimalType(deviceInfo.ecost / 100.0));
|
||||
updateState(CHANNEL_ENERGY_PER_MILE, new DecimalType(deviceInfo.whPerMile));
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="juicenet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>JuiceNet Binding</name>
|
||||
<description>This is the binding supporting the JuiceNet EV charger.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,98 @@
|
||||
# binding
|
||||
|
||||
binding.juicenet.name = JuiceNet Binding
|
||||
binding.juicenet.description = This is the binding supporting the JuiceNet EV charger.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.juicenet.account.label = JuiceNet Account
|
||||
thing-type.juicenet.account.description = This is the account for which your device(s) are registered at home.juice.net.
|
||||
thing-type.juicenet.device.label = JuiceBox Charger
|
||||
thing-type.juicenet.device.description = JuiceBox EV Charger
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.juicenet.account.apiToken.label = API Token
|
||||
thing-type.config.juicenet.account.apiToken.description = API Token from the user profile page. (https://home.juice.net/Manage)
|
||||
thing-type.config.juicenet.account.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.juicenet.account.refreshInterval.description = Interval the device is polled in seconds.
|
||||
thing-type.config.juicenet.device.unitID.label = Unit ID
|
||||
thing-type.config.juicenet.device.unitID.description = EV charger Unit ID from the JuiceNet webpage. (https://home.juice.net)
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.juicenet.carBatteryRange.label = Mileage Range
|
||||
channel-type.juicenet.carBatteryRange.description = Car distance range.
|
||||
channel-type.juicenet.carBatterySize.label = Car Battery Pack Size
|
||||
channel-type.juicenet.carBatterySize.description = Car battery pack size.
|
||||
channel-type.juicenet.carChargingRate.label = Car Charging Rate
|
||||
channel-type.juicenet.carChargingRate.description = Car charging rate.
|
||||
channel-type.juicenet.carDescription.label = Car Description
|
||||
channel-type.juicenet.carDescription.description = Car description of vehicle currently or last charged.
|
||||
channel-type.juicenet.chargingState.label = Charging State
|
||||
channel-type.juicenet.chargingState.description = The charging state (Start Charging, Smart Charging, Stop Charging).
|
||||
channel-type.juicenet.chargingState.state.option.start = Start Charging
|
||||
channel-type.juicenet.chargingState.state.option.smart = Smart Charging
|
||||
channel-type.juicenet.chargingState.state.option.stop = Stop Charging
|
||||
channel-type.juicenet.chargingTime.label = Charging Time
|
||||
channel-type.juicenet.chargingTime.description = Charging time since plug-in time.
|
||||
channel-type.juicenet.chargingTimeLeft.label = Charging Time Left
|
||||
channel-type.juicenet.chargingTimeLeft.description = Charging time left.
|
||||
channel-type.juicenet.current.label = Current
|
||||
channel-type.juicenet.current.description = Current charging current.
|
||||
channel-type.juicenet.currentLimit.label = Current Limit
|
||||
channel-type.juicenet.currentLimit.description = Max charging current allowed.
|
||||
channel-type.juicenet.ecost.label = Utility Energy Cost
|
||||
channel-type.juicenet.ecost.description = Cost of electricity from utility company. (currency / kWh)
|
||||
channel-type.juicenet.energy.label = Current Energy
|
||||
channel-type.juicenet.energy.description = Current power level of vehicle.
|
||||
channel-type.juicenet.energyAtPlugin.label = Energy at Plugin
|
||||
channel-type.juicenet.energyAtPlugin.description = Energy value at the plugging time.
|
||||
channel-type.juicenet.energyPerMile.label = Energy Hours Per Mile
|
||||
channel-type.juicenet.energyPerMile.description = Energy Hours Per Mile.
|
||||
channel-type.juicenet.energyToAdd.label = Energy to Add
|
||||
channel-type.juicenet.energyToAdd.description = Amount of energy to be added in current session.
|
||||
channel-type.juicenet.fuelConsumption.label = Fuel consumption
|
||||
channel-type.juicenet.fuelConsumption.description = Distance per volume (mpg) used in savings calculations.
|
||||
channel-type.juicenet.gasCost.label = Gas Cost
|
||||
channel-type.juicenet.gasCost.description = Cost of gasoline used in savings calculations.
|
||||
channel-type.juicenet.lifetimeEnergy.label = Lifetime Energy
|
||||
channel-type.juicenet.lifetimeEnergy.description = Total energy delivered to vehicles during lifetime.
|
||||
channel-type.juicenet.lifetimeSavings.label = Lifetime Savings
|
||||
channel-type.juicenet.lifetimeSavings.description = EV driving saving during lifetime.
|
||||
channel-type.juicenet.message.label = State Message
|
||||
channel-type.juicenet.message.description = This is a message detailing the state of the EV charger.
|
||||
channel-type.juicenet.name.label = Name
|
||||
channel-type.juicenet.name.description = Juice Box name.
|
||||
channel-type.juicenet.override.label = Override State
|
||||
channel-type.juicenet.override.description = Smart charging is overridden.
|
||||
channel-type.juicenet.plugUnplugTime.label = Plug/Unplug Time
|
||||
channel-type.juicenet.plugUnplugTime.description = Last time of either plug-in or plug-out.
|
||||
channel-type.juicenet.plugUnplugTime.state.pattern = %1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp
|
||||
channel-type.juicenet.power.label = Charging Power
|
||||
channel-type.juicenet.power.description = Current charging power.
|
||||
channel-type.juicenet.savings.label = Savings
|
||||
channel-type.juicenet.savings.description = Current session EV savings.
|
||||
channel-type.juicenet.state.label = Device State
|
||||
channel-type.juicenet.state.description = This is the current device state (Available, Plugged-In, Charging, Error, Disconnected).
|
||||
channel-type.juicenet.state.state.option.standby = Available
|
||||
channel-type.juicenet.state.state.option.plugged = Plugged-In
|
||||
channel-type.juicenet.state.state.option.charging = Charging
|
||||
channel-type.juicenet.state.state.option.error = Error
|
||||
channel-type.juicenet.state.state.option.disconnect = Disconnected
|
||||
channel-type.juicenet.targetTime.label = Target Time
|
||||
channel-type.juicenet.targetTime.description = “Start charging” start time, or time to start when overriding smart charging.
|
||||
channel-type.juicenet.targetTime.state.pattern = %1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp
|
||||
channel-type.juicenet.temperature.label = Temperature
|
||||
channel-type.juicenet.temperature.description = Current temperature at the unit.
|
||||
channel-type.juicenet.unitTime.label = Unit Time
|
||||
channel-type.juicenet.unitTime.description = Current time on the unit.
|
||||
channel-type.juicenet.unitTime.state.pattern = %1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp
|
||||
channel-type.juicenet.voltage.label = Voltage
|
||||
channel-type.juicenet.voltage.description = Current voltage.
|
||||
|
||||
# offline configuration errors
|
||||
|
||||
offline.configuration-error.id-missing = Must include an id in the configuration for the device.
|
||||
offline.configuration-error.non-existent-device = Device does not exist as part of this JuiceNet Account
|
||||
offline.configuration-error.bridge-missing = The JuiceBox device must be associated with a JuiceNet Account bridge
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="juicenet"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="account">
|
||||
<label>JuiceNet Account</label>
|
||||
<description>This is the account for which your device(s) are registered at home.juice.net.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="apiToken" type="text" required="true">
|
||||
<label>API Token</label>
|
||||
<description>API Token from the user profile page. (https://home.juice.net/Manage) </description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" unit="s" min="60">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Interval the device is polled in seconds.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,288 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="juicenet"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>JuiceBox Charger</label>
|
||||
<description>JuiceBox EV Charger</description>
|
||||
|
||||
<channels>
|
||||
<channel id="name" typeId="name"/>
|
||||
<channel id="chargingState" typeId="chargingState"/>
|
||||
|
||||
<channel id="state" typeId="state"/>
|
||||
<channel id="message" typeId="message"/>
|
||||
<channel id="override" typeId="override"/>
|
||||
<channel id="chargingTimeLeft" typeId="chargingTimeLeft"/>
|
||||
<channel id="plugUnplugTime" typeId="plugUnplugTime"/>
|
||||
<channel id="targetTime" typeId="targetTime"/>
|
||||
<channel id="unitTime" typeId="unitTime"/>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="currentLimit" typeId="currentLimit"/>
|
||||
<channel id="current" typeId="current"/>
|
||||
<channel id="voltage" typeId="voltage"/>
|
||||
<channel id="energy" typeId="energy"/>
|
||||
<channel id="savings" typeId="savings"/>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="chargingTime" typeId="chargingTime"/>
|
||||
<channel id="energyAtPlugin" typeId="energyAtPlugin"/>
|
||||
<channel id="energyToAdd" typeId="energyToAdd"/>
|
||||
|
||||
<channel id="lifetimeEnergy" typeId="lifetimeEnergy"/>
|
||||
<channel id="lifetimeSavings" typeId="lifetimeSavings"/>
|
||||
|
||||
<channel id="gasCost" typeId="gasCost"/>
|
||||
<channel id="fuelConsumption" typeId="fuelConsumption"/>
|
||||
<channel id="ecost" typeId="ecost"/>
|
||||
<channel id="energyPerMile" typeId="energyPerMile"/>
|
||||
|
||||
<channel id="carDescription" typeId="carDescription"/>
|
||||
<channel id="carBatterySize" typeId="carBatterySize"/>
|
||||
<channel id="carBatteryRange" typeId="carBatteryRange"/>
|
||||
<channel id="carChargingRate" typeId="carChargingRate"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="name"></property>
|
||||
</properties>
|
||||
<representation-property>unitID</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="unitID" type="text" required="true">
|
||||
<label>Unit ID</label>
|
||||
<description>EV charger Unit ID from the JuiceNet webpage. (https://home.juice.net) </description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="name">
|
||||
<item-type>String</item-type>
|
||||
<label>Name</label>
|
||||
<description>Juice Box name.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chargingState">
|
||||
<item-type>String</item-type>
|
||||
<label>Charging State</label>
|
||||
<description>The charging state (Start Charging, Smart Charging, Stop Charging).</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="start">Start Charging</option>
|
||||
<option value="smart">Smart Charging</option>
|
||||
<option value="stop">Stop Charging</option>
|
||||
</options>
|
||||
</state>
|
||||
<autoUpdatePolicy>recommend</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="state">
|
||||
<item-type>String</item-type>
|
||||
<label>Device State</label>
|
||||
<description>This is the current device state (Available, Plugged-In, Charging, Error, Disconnected).</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="standby">Available</option>
|
||||
<option value="plugged">Plugged-In</option>
|
||||
<option value="charging">Charging</option>
|
||||
<option value="error">Error</option>
|
||||
<option value="disconnect">Disconnected</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="message">
|
||||
<item-type>String</item-type>
|
||||
<label>State Message</label>
|
||||
<description>This is a message detailing the state of the EV charger.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="override">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Override State</label>
|
||||
<description>Smart charging is overridden.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chargingTimeLeft">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Charging Time Left</label>
|
||||
<description>Charging time left.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="plugUnplugTime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Plug/Unplug Time</label>
|
||||
<description>Last time of either plug-in or plug-out.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="targetTime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Target Time</label>
|
||||
<description>“Start charging” start time, or time to start when overriding smart charging.</description>
|
||||
<category>Time</category>
|
||||
<state pattern="%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="unitTime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Unit Time</label>
|
||||
<description>Current time on the unit.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%1$tB %1$te, %1$tY %1$tl:%1$tM %1$tp"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Current temperature at the unit.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="currentLimit">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Current Limit</label>
|
||||
<description>Max charging current allowed.</description>
|
||||
<state pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Current</label>
|
||||
<description>Current charging current.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="voltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>Current voltage.</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Current Energy</label>
|
||||
<description>Current power level of vehicle.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="savings">
|
||||
<item-type>Number</item-type>
|
||||
<label>Savings</label>
|
||||
<description>Current session EV savings.</description>
|
||||
<state readOnly="true" pattern="$%.2f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Charging Power</label>
|
||||
<description>Current charging power.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chargingTime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Charging Time</label>
|
||||
<description>Charging time since plug-in time.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyAtPlugin">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy at Plugin</label>
|
||||
<description>Energy value at the plugging time.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyToAdd">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy to Add</label>
|
||||
<description>Amount of energy to be added in current session.</description>
|
||||
<state pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lifetimeEnergy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Lifetime Energy</label>
|
||||
<description>Total energy delivered to vehicles during lifetime.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lifetimeSavings">
|
||||
<item-type>Number</item-type>
|
||||
<label>Lifetime Savings</label>
|
||||
<description>EV driving saving during lifetime.</description>
|
||||
<state readOnly="true" pattern="$%.2f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="gasCost">
|
||||
<item-type>Number</item-type>
|
||||
<label>Gas Cost</label>
|
||||
<description>Cost of gasoline used in savings calculations.</description>
|
||||
<state readOnly="true" pattern="$%.2f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fuelConsumption">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fuel consumption</label>
|
||||
<description>Distance per volume (mpg) used in savings calculations.</description>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ecost">
|
||||
<item-type>Number</item-type>
|
||||
<label>Utility Energy Cost</label>
|
||||
<description>Cost of electricity from utility company. (currency / kWh)</description>
|
||||
<state readOnly="true" pattern="$%.2f"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="energyPerMile">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Energy Hours Per Mile</label>
|
||||
<description>Energy Hours Per Mile.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carDescription">
|
||||
<item-type>String</item-type>
|
||||
<label>Car Description</label>
|
||||
<description>Car description of vehicle currently or last charged.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carBatterySize">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Car Battery Pack Size</label>
|
||||
<description>Car battery pack size.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carBatteryRange">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Mileage Range</label>
|
||||
<description>Car distance range.</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carChargingRate">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Car Charging Rate</label>
|
||||
<description>Car charging rate.</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -186,6 +186,7 @@
|
||||
<module>org.openhab.binding.jablotron</module>
|
||||
<module>org.openhab.binding.jeelink</module>
|
||||
<module>org.openhab.binding.jellyfin</module>
|
||||
<module>org.openhab.binding.juicenet</module>
|
||||
<module>org.openhab.binding.kaleidescape</module>
|
||||
<module>org.openhab.binding.keba</module>
|
||||
<module>org.openhab.binding.km200</module>
|
||||
|
Loading…
Reference in New Issue
Block a user