[homeconnect] Initial contribution (#9187)

Signed-off-by: Jonas Brüstel <jonas@bruestel.net>
Co-authored-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
bruestel
2021-05-13 14:56:03 +02:00
committed by GitHub
parent e1a76505a0
commit a1a990989e
92 changed files with 9521 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="homeconnect" 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>Home Connect Binding</name>
<description>The binding integrates the Home Connect (https://www.home-connect.com/) system into openHAB. It connects
to household devices from brands like Bosch and Siemens.</description>
</binding:binding>

View File

@@ -0,0 +1,658 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="homeconnect"
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">
<!-- Home Connect API Bridge -->
<bridge-type id="api_bridge">
<label>Home Connect API</label>
<description>This bridge represents the gateway to the Home Connect API.</description>
<config-description>
<parameter name="clientId" type="text" required="true">
<label>Client Id</label>
<description>Application client id</description>
</parameter>
<parameter name="clientSecret" type="text" required="true">
<label>Client Secret</label>
<description>Application client secret</description>
<context>password</context>
</parameter>
<parameter name="simulator" type="boolean" required="true">
<label>Use Simulator Environment</label>
<description>Use simulated environment at https://developer.home-connect.com/simulator/</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<!-- Dishwasher -->
<thing-type id="dishwasher">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Dishwasher</label>
<description>Home Connect connected dishwasher (e.g. Bosch or Siemens).</description>
<channels>
<channel id="power_state" typeId="system.power"/>
<channel id="door_state" typeId="door_state"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="remaining_program_time_state" typeId="remaining_program_time_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
<channel id="ambient_light_state" typeId="ambient_light_state"/>
<channel id="ambient_light_brightness_state" typeId="system.brightness"/>
<channel id="ambient_light_color_state" typeId="ambient_light_color_state"/>
<channel id="ambient_light_custom_color_state" typeId="ambient_light_custom_color_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Oven -->
<thing-type id="oven">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Oven</label>
<description>Home Connect connected oven (e.g. Bosch or Siemens).</description>
<channels>
<channel id="power_state" typeId="system.power"/>
<channel id="door_state" typeId="door_state"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="setpoint_temperature" typeId="setpoint_temperature"/>
<channel id="duration" typeId="duration"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="remaining_program_time_state" typeId="remaining_program_time_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
<channel id="oven_current_cavity_temperature" typeId="oven_current_cavity_temperature"/>
<channel id="elapsed_program_time" typeId="elapsed_program_time"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Washer -->
<thing-type id="washer">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Washer</label>
<description>Home Connect connected washing machine (e.g. Bosch or Siemens).</description>
<channels>
<channel id="door_state" typeId="door_state"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="laundry_care_washer_temperature" typeId="laundry_care_washer_temperature"/>
<channel id="laundry_care_washer_spin_speed" typeId="laundry_care_washer_spin_speed"/>
<channel id="laundry_care_washer_idos1" typeId="laundry_care_washer_idos1"/>
<channel id="laundry_care_washer_idos2" typeId="laundry_care_washer_idos2"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="remaining_program_time_state" typeId="remaining_program_time_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Washer dryer combination -->
<thing-type id="washerdryer">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Washer Dryer Combination</label>
<description>Home Connect connected combined washer dryer appliance.</description>
<channels>
<channel id="door_state" typeId="door_state"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="laundry_care_washer_temperature" typeId="laundry_care_washer_temperature"/>
<channel id="laundry_care_washer_spin_speed" typeId="laundry_care_washer_spin_speed"/>
<channel id="dryer_drying_target" typeId="dryer_drying_target"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="remaining_program_time_state" typeId="remaining_program_time_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Dryer -->
<thing-type id="dryer">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Dryer</label>
<description>Home Connect connected dryer (e.g. Bosch or Siemens).</description>
<channels>
<channel id="door_state" typeId="door_state"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="dryer_drying_target" typeId="dryer_drying_target"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="remaining_program_time_state" typeId="remaining_program_time_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Fridge Freezer -->
<thing-type id="fridgefreezer">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Refrigerator / Freezer</label>
<description>Home Connect connected refrigerator/freezer (e.g. Bosch or Siemens).</description>
<channels>
<channel id="door_state" typeId="door_state"/>
<channel id="setpoint_temperature_refrigerator" typeId="setpoint_temperature_refrigerator"/>
<channel id="super_mode_refrigerator" typeId="super_mode_refrigerator"/>
<channel id="setpoint_temperature_freezer" typeId="setpoint_temperature_freezer"/>
<channel id="super_mode_freezer" typeId="super_mode_freezer"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Coffee Machine -->
<thing-type id="coffeemaker">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Coffee Machine</label>
<description>Home Connect connected coffee machine (e.g. Bosch or Siemens).</description>
<channels>
<channel id="power_state" typeId="system.power"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
<channel id="basic_actions_state" typeId="basic_actions_state"/>
<channel id="program_progress_state" typeId="program_progress_state"/>
<channel id="coffeemaker_drip_tray_full_state" typeId="coffeemaker_drip_tray_full_state"/>
<channel id="coffeemaker_water_tank_empty_state" typeId="coffeemaker_water_tank_empty_state"/>
<channel id="coffeemaker_bean_container_empty_state" typeId="coffeemaker_bean_container_empty_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Hood -->
<thing-type id="hood">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Hood</label>
<description>Home Connect connected kitchen hood.</description>
<channels>
<channel id="power_state" typeId="system.power"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_start_allowance_state" typeId="remote_start_allowance_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="hood_venting_level" typeId="hood_venting_level"/>
<channel id="hood_intensive_level" typeId="hood_intensive_level"/>
<channel id="hood_program_state" typeId="hood_program_state"/>
<channel id="ambient_light_state" typeId="ambient_light_state"/>
<channel id="ambient_light_brightness_state" typeId="system.brightness"/>
<channel id="ambient_light_color_state" typeId="ambient_light_color_state"/>
<channel id="ambient_light_custom_color_state" typeId="ambient_light_custom_color_state"/>
<channel id="functional_light_state" typeId="functional_light_state"/>
<channel id="functional_light_brightness_state" typeId="system.brightness"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Cooktop -->
<thing-type id="hob">
<supported-bridge-type-refs>
<bridge-type-ref id="api_bridge"/>
</supported-bridge-type-refs>
<label>Cooktop</label>
<description>Home Connect connected kitchen cooktop (hob).</description>
<channels>
<channel id="power_state" typeId="system.power"/>
<channel id="operation_state" typeId="operation_state"/>
<channel id="remote_control_active_state" typeId="remote_control_active_state"/>
<channel id="local_control_active_state" typeId="local_control_active_state"/>
<channel id="active_program_state" typeId="active_program_state"/>
<channel id="selected_program_state" typeId="selected_program_state"/>
</channels>
<representation-property>haId</representation-property>
<config-description>
<parameter name="haId" type="text" required="true">
<label>haId</label>
<description>Unique identifier representing a specific home appliance.</description>
</parameter>
</config-description>
</thing-type>
<!-- Channel types -->
<channel-type id="basic_actions_state">
<item-type>String</item-type>
<label>Program Actions</label>
<state>
<options>
<option value="start">Start program</option>
<option value="stop">Stop program</option>
</options>
</state>
</channel-type>
<channel-type id="local_control_active_state">
<item-type>Switch</item-type>
<label>Local Control State</label>
<description>This status indicates whether the home appliance is currently manually controlled by the user operating
the home appliance, e.g. opening the door or pressing a button.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="coffeemaker_drip_tray_full_state">
<item-type>Switch</item-type>
<label>Drip Tray Full</label>
<description>Is coffee maker drip tray full?</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="coffeemaker_water_tank_empty_state">
<item-type>Switch</item-type>
<label>Water Tank Empty</label>
<description>Is coffee maker water tank empty?</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="coffeemaker_bean_container_empty_state">
<item-type>Switch</item-type>
<label>Bean Container Empty</label>
<description>Is coffee maker bean container is empty?</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="door_state">
<item-type>Contact</item-type>
<label>Door State</label>
<description>This status describes the door state of a home appliance. A status change is either triggered by the user
operating the home appliance locally (i.e. opening/closing door) or automatically by the home appliance (i.e. locking
the door).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="operation_state">
<item-type>String</item-type>
<label>Operation State</label>
<description>This status describes the operation state of the home appliance.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="remote_start_allowance_state">
<item-type>Switch</item-type>
<label>Remote Start Allowance State</label>
<description>This status indicates whether the remote program start is enabled. This can happen due to a programmatic
change (only disabling), or manually by the user changing the flag locally on the home appliance, or automatically
after a certain duration - usually 24 hours.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="remote_control_active_state">
<item-type>Switch</item-type>
<label>Remote Control Activation State</label>
<description>This status indicates whether the allowance for remote controlling is enabled.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="active_program_state">
<item-type>String</item-type>
<label>Active Program</label>
<description>This status describes the active program of the home appliance.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="selected_program_state">
<item-type>String</item-type>
<label>Selected Program</label>
<description>This state describes the selected program of the home appliance.</description>
</channel-type>
<channel-type id="remaining_program_time_state">
<item-type>Number:Time</item-type>
<label>Remaining Program Time</label>
<description>This status indicates the remaining program time of the home appliance.</description>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="program_progress_state">
<item-type>Number:Dimensionless</item-type>
<label>Progress State</label>
<description>This status describes the program progress of the home appliance.</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="oven_current_cavity_temperature">
<item-type>Number:Temperature</item-type>
<label>Cavity Temperature</label>
<description>This status describes the oven cavity temperature of the home appliance.</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="elapsed_program_time">
<item-type>Number:Time</item-type>
<label>Elapsed Program Time</label>
<description>This status describes the elapsed program time of the home appliance.</description>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="setpoint_temperature">
<item-type>Number:Temperature</item-type>
<label>Setpoint Temperature</label>
<description>This status describes the intended cooking compartment temperature of the home appliance.</description>
<state pattern="%.0f %unit%" step="1"/>
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<label>Selected Duration</label>
<description>This status describes the duration of the program of the home appliance.</description>
<state pattern="%d %unit%" step="60" min="60"/>
</channel-type>
<channel-type id="laundry_care_washer_temperature">
<item-type>String</item-type>
<label>Washing Program Temperature</label>
<description>This status describes the temperature of the washing program of the home appliance.</description>
<state>
<options>
<option value="LaundryCare.Washer.EnumType.Temperature.Cold">Cold water</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC20">20 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC30">30 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC40">40 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC50">50 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC60">60 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC70">70 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC80">80 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.GC90">90 °C</option>
<option value="LaundryCare.Washer.EnumType.Temperature.Auto">Auto</option>
<option value="LaundryCare.Washer.EnumType.Temperature.UlCold">Cold (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.Temperature.UlWarm">Warm (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.Temperature.UlHot">Hot (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.Temperature.UlExtraHot">Extra hot (US/CA)</option>
</options>
</state>
</channel-type>
<channel-type id="laundry_care_washer_spin_speed">
<item-type>String</item-type>
<label>Spin Speed</label>
<description>This status defines the spin speed of a washer program of the home appliance.</description>
<state>
<options>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.Off">No spinning</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM400">400 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM600">600 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM800">800 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM1000">1000 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM1200">1200 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM1400">1400 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.RPM1600">1600 rpm</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.Auto">Auto</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.UlNo">No spinning (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.UlLow">Low (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.UlMedium">Medium (US/CA)</option>
<option value="LaundryCare.Washer.EnumType.SpinSpeed.UlHigh">High (US/CA)</option>
</options>
</state>
</channel-type>
<channel-type id="laundry_care_washer_idos1">
<item-type>String</item-type>
<label>i-Dos 1 Dosing Level</label>
<description>This status defines the i-Dos dosing level of a washer program of the home appliance. (If appliance
supports i-Dos)</description>
<state>
<options>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Off">Off</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Light">Light</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Normal">Normal</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Strong">Strong</option>
</options>
</state>
</channel-type>
<channel-type id="laundry_care_washer_idos2">
<item-type>String</item-type>
<label>i-Dos 2 Dosing Level</label>
<description>This status defines the i-Dos dosing level of a washer program of the home appliance. (If appliance
supports i-Dos)</description>
<state>
<options>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Off">Off</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Light">Light</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Normal">Normal</option>
<option value="LaundryCare.Washer.EnumType.IDosingLevel.Strong">Strong</option>
</options>
</state>
</channel-type>
<channel-type id="setpoint_temperature_refrigerator">
<item-type>Number:Temperature</item-type>
<label>Refrigerator Temperature</label>
<description>Target temperature of the refrigerator compartment (Range depends on appliance - common range 2 to 8°C).</description>
<state step="1" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="setpoint_temperature_freezer">
<item-type>Number:Temperature</item-type>
<label>Freezer temperature</label>
<description>Target temperature of the freezer compartment (Range depends on appliance - common range -16 to -24°C).</description>
<state step="1" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="super_mode_refrigerator">
<item-type>Switch</item-type>
<label>Refrigerator Super Mode</label>
<description>The setting has no impact on setpoint temperatures but will make the fridge compartment cool to the
lowest possible temperature until it is disabled manually by the customer or by the HA because of a timeout.</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="super_mode_freezer">
<item-type>Switch</item-type>
<label>Freezer Super Mode</label>
<description>This setting has no impact on setpoint temperatures but will make the freezer compartment cool to the
lowest possible temperature until it is disabled manually by the customer or by the home appliance because of a
timeout.</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="dryer_drying_target">
<item-type>String</item-type>
<label>Drying Target</label>
<description>Specifies the desired dryness setting.</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="hood_venting_level">
<item-type>String</item-type>
<label>Venting Level</label>
<description>Current venting level of the hood.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="hood_intensive_level">
<item-type>String</item-type>
<label>Intensive level</label>
<description>Current venting intensive level of the hood.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="hood_program_state">
<item-type>String</item-type>
<label>Program Actions</label>
<description>Start hood program.</description>
</channel-type>
<channel-type id="functional_light_state">
<item-type>Switch</item-type>
<label>Functional Light State</label>
<description>This setting describes the current functional light state of the home appliance.</description>
</channel-type>
<channel-type id="ambient_light_state">
<item-type>Switch</item-type>
<label>Ambient Light State</label>
<description>This setting describes the current ambient light state of the home appliance.</description>
</channel-type>
<channel-type id="ambient_light_color_state">
<item-type>String</item-type>
<label>Ambient Light Color</label>
<description>This setting describes the color state of the ambient light.</description>
<state readOnly="false">
<options>
<option value="BSH.Common.EnumType.AmbientLightColor.CustomColor">Custom Color</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color1">Color 1</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color2">Color 2</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color3">Color 3</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color4">Color 4</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color5">Color 5</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color6">Color 6</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color7">Color 7</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color8">Color 8</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color9">Color 9</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color10">Color 10</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color11">Color 11</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color12">Color 12</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color13">Color 13</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color14">Color 14</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color15">Color 15</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color16">Color 16</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color17">Color 17</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color18">Color 18</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color19">Color 19</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color20">Color 20</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color21">Color 21</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color22">Color 22</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color23">Color 23</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color24">Color 24</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color25">Color 25</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color26">Color 26</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color27">Color 27</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color28">Color 28</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color29">Color 29</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color30">Color 30</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color31">Color 31</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color32">Color 32</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color33">Color 33</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color34">Color 34</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color35">Color 35</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color36">Color 36</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color37">Color 37</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color38">Color 38</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color39">Color 39</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color40">Color 40</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color41">Color 41</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color42">Color 42</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color43">Color 43</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color44">Color 44</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color45">Color 45</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color46">Color 46</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color47">Color 47</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color48">Color 48</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color49">Color 49</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color50">Color 50</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color51">Color 51</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color52">Color 52</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color53">Color 53</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color54">Color 54</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color55">Color 55</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color56">Color 56</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color57">Color 57</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color58">Color 58</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color59">Color 59</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color60">Color 60</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color61">Color 61</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color62">Color 62</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color63">Color 63</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color64">Color 64</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color65">Color 65</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color66">Color 66</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color67">Color 67</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color68">Color 68</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color69">Color 69</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color70">Color 70</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color71">Color 71</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color72">Color 72</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color73">Color 73</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color74">Color 74</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color75">Color 75</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color76">Color 76</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color77">Color 77</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color78">Color 78</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color79">Color 79</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color80">Color 80</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color81">Color 81</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color82">Color 82</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color83">Color 83</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color84">Color 84</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color85">Color 85</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color86">Color 86</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color87">Color 87</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color88">Color 88</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color89">Color 89</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color90">Color 90</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color91">Color 91</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color92">Color 92</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color93">Color 93</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color94">Color 94</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color95">Color 95</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color96">Color 96</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color97">Color 97</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color98">Color 98</option>
<option value="BSH.Common.EnumType.AmbientLightColor.Color99">Color 99</option>
</options>
</state>
</channel-type>
<channel-type id="ambient_light_custom_color_state">
<item-type>Color</item-type>
<label>Ambient Light (Custom)</label>
<description>This setting describes the custom color state of the ambient light.</description>
<category>Colorpicker</category>
<tags>
<tag>Lighting</tag>
</tags>
<state readOnly="false"/>
</channel-type>
</thing:thing-descriptions>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar .nav-link.active {
color: #007bff;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/homeconnect/assets/favicon/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1176 2309 c-360 -38 -677 -264 -831 -591 -65 -138 -87 -235 -92
-403 -4 -125 -1 -167 15 -249 12 -54 24 -101 27 -104 3 -3 35 23 71 59 l65 65
-12 74 c-24 154 9 357 82 503 159 316 487 506 838 484 379 -24 702 -297 791
-667 24 -102 27 -281 6 -382 -70 -330 -344 -604 -674 -674 -103 -21 -281 -18
-383 7 -154 39 -289 113 -403 223 l-59 57 -59 -58 -58 -57 43 -44 c218 -225
565 -345 872 -302 619 86 1023 682 871 1282 -84 330 -328 603 -643 718 -147
53 -318 75 -467 59z"/>
<path d="M816 1299 c-251 -254 -456 -466 -456 -472 0 -13 72 -137 80 -137 3 0
194 189 425 420 l420 420 321 -321 322 -322 22 43 c12 24 28 60 35 80 l13 37
-357 357 c-196 196 -359 356 -363 356 -3 0 -211 -208 -462 -461z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,218 @@
/* globals Chart:false, feather:false, Plotly:false, requests:false */
(function () {
'use strict'
feather.replace();
$(".redirectUri").text(window.location.href.substring(0, window.location.href.lastIndexOf('/homeconnect') + 12));
$('#apiDetailModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var thingId = button.data('thing-id');
var action = button.data('api-action');
var titleText = button.data('title');
var modal = $(this);
var title = modal.find('.modal-title');
var subTitle = modal.find('.modal-subtitle');
var responseBodyElement = modal.find('.modal-response-body');
responseBodyElement.text('Loading...');
title.text(titleText);
subTitle.text(thingId);
modal.modal('handleUpdate');
let jqxhr = $.get('appliances?thingId=' + thingId + '&action=' + action, function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
});
jqxhr.fail(function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
})
jqxhr.always(function () {
modal.modal('handleUpdate');
});
})
$('#rawCommandDetailModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var thingId = button.data('thing-id');
var haId = button.data('ha-id');
var modal = $(this);
var subTitle = modal.find('.modal-subtitle');
var inputPath = modal.find('#raw-path');
var inputBody = modal.find('#raw-request-body');
var submit = modal.find('#raw-submit');
var responseBodyElement = modal.find('.modal-response-body');
var responseTitle = modal.find('.raw-response-header');
subTitle.text(thingId);
responseBodyElement.text('');
responseTitle.hide();
inputPath.val('/api/homeappliances/' + haId + '/programs/active')
modal.modal('handleUpdate');
submit.click(function () {
responseBodyElement.text('Loading...');
let jqxhr = $.post('appliances?thingId=' + thingId + '&action=put-raw&path=' + inputPath.val(),
inputBody.val(), function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
responseTitle.show();
});
jqxhr.fail(function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
responseTitle.show();
})
jqxhr.always(function () {
modal.modal('handleUpdate');
});
});
})
$('#rawGetDetailModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var thingId = button.data('thing-id');
var haId = button.data('ha-id');
var modal = $(this);
var subTitle = modal.find('.modal-subtitle');
var inputPath = modal.find('#raw-get-path');
var submit = modal.find('#raw-get-submit');
var responseBodyElement = modal.find('.modal-response-body');
var responseTitle = modal.find('.raw-response-header');
subTitle.text(thingId);
responseBodyElement.text('');
responseTitle.hide();
inputPath.val('/api/homeappliances/' + haId + '/programs')
modal.modal('handleUpdate');
submit.click(function () {
responseBodyElement.text('Loading...');
let jqxhr = $.post('appliances?thingId=' + thingId + '&action=get-raw&path=' + inputPath.val(), function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
responseTitle.show();
});
jqxhr.fail(function (data) {
responseBodyElement.text(JSON.stringify(data, null, '\t'));
responseTitle.show();
})
jqxhr.always(function () {
modal.modal('handleUpdate');
});
});
})
$('#requestDetailModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var requestId = button.data('request-id');
var request = requests.find(item => item.id == requestId);
var requestHeader = request.homeConnectRequest.header;
var requestBody = request.homeConnectRequest.body;
var modal = $(this);
var requestBodyElement = modal.find('.modal-request-body');
var title = modal.find('.modal-title');
var responseBodyElement = modal.find('.modal-response-body');
var requestHeaderElement = modal.find('.modal-request-header');
var responseHeaderElement = modal.find('.modal-response-header');
title.text(request.homeConnectRequest.method + ' ' + request.homeConnectRequest.url);
if (requestBody) {
requestBodyElement.text(requestBody);
requestBodyElement.removeClass('text-muted')
} else {
requestBodyElement.text('Empty request body');
requestBodyElement.addClass('text-muted')
}
if (request.homeConnectResponse && request.homeConnectResponse.body) {
responseBodyElement.text(request.homeConnectResponse.body);
responseBodyElement.removeClass('text-muted')
} else {
responseBodyElement.text('Empty response body');
responseBodyElement.addClass('text-muted')
}
responseHeaderElement.empty();
if (request.homeConnectResponse && request.homeConnectResponse.header) {
var responseHeader = request.homeConnectResponse.header;
Object.keys(responseHeader).forEach(key => {
console.log(`key=${key} value=${responseHeader[key]}`);
responseHeaderElement.append($(`<dt class="col-sm-4">${key}</dt>`));
responseHeaderElement.append($(`<dd class="col-sm-8 text-break">${responseHeader[key]}</dd>`));
responseHeaderElement.append($('<div class="w-100"></div>'));
});
}
requestHeaderElement.empty();
Object.keys(requestHeader).forEach(key => {
console.log(`key=${key} value=${requestHeader[key]}`);
requestHeaderElement.append($(`<dt class="col-sm-4">${key}</dt>`));
requestHeaderElement.append($(`<dd class="col-sm-8 text-break">${requestHeader[key]}</dd>`));
requestHeaderElement.append($('<div class="w-100"></div>'));
});
modal.modal('handleUpdate');
})
$('.reload-page').click(function () {
location.reload();
});
$('.request-chart').each(function (index, element) {
var bridgeId = $(this).data('bridge-id');
var chartElement = element;
function makeplot (bridgeId, chartElement) {
Plotly.d3.csv('requests?bridgeId=' + bridgeId + '&action=request-csv', function (data) {
processData(data, chartElement)
});
}
function processData (allRows, chartElement) {
console.log(allRows);
var x = [], y = [], standardDeviation = [];
for (var i = 0; i < allRows.length; i++) {
var row = allRows[i];
x.push(row['time']);
y.push(row['requests']);
}
console.log('X', x, 'Y', y, 'SD', standardDeviation);
makePlotly(x, y, standardDeviation, chartElement);
}
function makePlotly (x, y, standard_deviation, chartElement){
var traces = [{
x: x,
y: y,
type: 'histogram',
histfunc: 'sum',
xbins: {
size: 1000
}
}];
Plotly.newPlot(chartElement, traces,
{
xaxis: {
rangemode: 'nonnegative',
autorange: true,
title: '',
type: 'date'
},
yaxis: {
title: 'requests',
rangemode: 'nonnegative'
}
},
{
displayModeBar: false,
responsive: true
});
}
makeplot(bridgeId, chartElement);
});
}())

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="base :: head"></head>
<body>
<nav th:replace="base :: topNav"></nav>
<div class="container-fluid">
<div class="row">
<nav th:replace="base :: sidebarMenu (current='appliances')"></nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Appliances</h1>
</div>
<div th:if="${bridgeHandlers.size() == 0}">No Home Connect bridge found. Please manually add 'Home Connect API' bridge and authorize it here.</div>
<div th:each="bridgeHandler: ${bridgeHandlers}">
<div th:each="thingHandler: ${bridgeHandler.getThingHandler()}">
<div class="card mb-3" th:with="events=${bridgeHandler.getEventSourceClient().getLatestEvents(thingHandler.getThingHaId())}, thing=${thingHandler.getThing()}, status=${thingHandler.getThing().getStatus().toString()}, uid=${thingHandler.getThing().getUID().getAsString()}, haId=${thingHandler.getThingHaId()}">
<div class="card-body">
<section th:id="${uid}">
<h5 class="card-title" th:text="${thing.getLabel()}">Card title</h5>
</section>
<h6 class="card-subtitle mb-2 text-muted" th:text="${uid}">Card subtitle</h6>
<dl class="row">
<dt class="col-sm-2">Bridge</dt>
<dd class="col-sm-8" th:text="${bridgeHandler.getThing().getLabel() + ' (' + bridgeHandler.getThing().getUID().getAsString() + ')'}">1234567890</dd>
<div class="w-100"></div>
<dt class="col-sm-2">HaId</dt>
<dd class="col-sm-8" th:text="${haId}">1234567890</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Status</dt>
<dd class="col-sm-8">
<span class="badge" th:classappend="|${status == 'UNINITIALIZED' ? 'badge-warning' : ''} ${status == 'OFFLINE' ? 'badge-danger' : ''} ${status == 'ONLINE' ? 'badge-success' : ''}|" th:text="${status}">OFFLINE</span>
</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Accessible (SSE)</dt>
<dd class="col-sm-8">
<span class="badge" th:classappend="${thingHandler.isThingAccessibleViaServerSentEvents() ? 'badge-success' : 'badge-danger'}" th:text="${thingHandler.isThingAccessibleViaServerSentEvents() ? 'TRUE' : 'FALSE'}">OFFLINE</span>
</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Last Event received (SSE)</dt>
<dd class="col-sm-8" th:if="${events.size() > 0}" th:text="${#temporals.format(events.get(events.size()-1).creation, 'yyyy-MM-dd HH:mm:ss.SSS Z')}">1234567890</dd>
<dd class="col-sm-8" th:unless="${events.size() > 0}">unknown</dd>
<div class="w-100"></div>
<dt class="col-sm-2">API Actions</dt>
<dd class="col-sm-8">
<div class="mb-1">
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-primary btn-sm py-0" data-title="Appliance Details" data-api-action="show-details" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Appliance Details
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-primary btn-sm py-0" data-title="Operation State" data-api-action="operation-state" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Operation State
</button>
<div class="w-100"></div>
</div>
<div class="mb-1">
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-secondary btn-sm py-0" data-title="All Programs" data-api-action="all-programs" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
All Programs
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-secondary btn-sm py-0" data-title="Available Programs" data-api-action="available-programs" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Available Programs
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-secondary btn-sm py-0" data-title="Selected Program" data-api-action="selected-program" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Selected Program
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-secondary btn-sm py-0" data-title="Active Program" data-api-action="active-program" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Active Program
</button>
<div class="w-100"></div>
</div>
<div class="mb-1">
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-info btn-sm py-0" data-title="Power State" data-api-action="power-state" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Power State
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-info btn-sm py-0" data-title="Door State" data-api-action="door-state" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Door State
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-info btn-sm py-0" data-title="Remote Control Start Allowed" data-api-action="remote-control-start-allowed" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Remote Control Start Allowed
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-info btn-sm py-0" data-title="Remote Control Active" data-api-action="remote-control-active" th:attr="data-thing-id=${uid}" data-toggle="modal" data-target="#apiDetailModal">
Remote Control Active
</button>
</div>
<div>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-warning btn-sm py-0" data-title="Send Raw Command (PUT)" th:attr="data-thing-id=${uid}, data-ha-id=${haId}" data-toggle="modal" data-target="#rawCommandDetailModal">
Send Raw Command (PUT)
</button>
<button style="display: inline-block;font-size: 0.8em;" type="button" class="btn btn-warning btn-sm py-0" data-title="Send Raw Request (GET)" th:attr="data-thing-id=${uid}, data-ha-id=${haId}" data-toggle="modal" data-target="#rawGetDetailModal">
Send Raw Request (GET)
</button>
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- API action detail modal -->
<div class="modal fade" id="apiDetailModal" tabindex="-1" role="dialog" aria-labelledby="apiDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div>
<h5 class="modal-title text-truncate" id="apiDetailModalLabel">API</h5>
<h6 class="modal-subtitle mb-2 text-muted text-truncate">Card subtitle</h6>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pre class="modal-response-body"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Send Raw Command modal -->
<div class="modal fade" id="rawCommandDetailModal" tabindex="-1" role="dialog" aria-labelledby="rawCommandDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div>
<h5 class="modal-title text-truncate" id="rawCommandDetailModalLabel">Send Raw Command</h5>
<h6 class="modal-subtitle mb-2 text-muted text-truncate">Card subtitle</h6>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="raw-path" class="col-form-label">Path:</label>
<input type="text" class="form-control" id="raw-path">
</div>
<div class="form-group">
<label for="raw-request-body" class="col-form-label">Request Body:</label>
<textarea cols=100 rows=12 class="form-control" id="raw-request-body"></textarea>
</div>
</form>
<label class="raw-response-header">Response:</label>
<pre class="modal-response-body"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="raw-submit">Submit Request</button>
</div>
</div>
</div>
</div>
<!-- Send Raw Request modal -->
<div class="modal fade" id="rawGetDetailModal" tabindex="-1" role="dialog" aria-labelledby="rawGetDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div>
<h5 class="modal-title text-truncate" id="rawGetDetailModalLabel">Send Request (GET)</h5>
<h6 class="modal-subtitle mb-2 text-muted text-truncate">Card subtitle</h6>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="raw-path" class="col-form-label">Path:</label>
<input type="text" class="form-control" id="raw-get-path">
</div>
</form>
<label class="raw-response-header">Response:</label>
<pre class="modal-response-body"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="raw-get-submit">Submit Request</button>
</div>
</div>
</div>
</div>
</div>
<!--/*/ <th:block th:include="base :: js">
</th:block> /*/-->
</body>
</html>

View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Home Connect Console</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/homeconnect/asset/css/bootstrap.min.css}" rel="stylesheet">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="180x180" th:href="@{/homeconnect/asset/favicon/apple-touch-icon.png}">
<link rel="icon" type="image/png" sizes="32x32" th:href="@{/homeconnect/asset/favicon/favicon-32x32.png}">
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/homeconnect/asset/favicon/favicon-16x16.png}">
<link rel="manifest" th:href="@{/homeconnect/asset/favicon/site.webmanifest}">
<link rel="mask-icon" th:href="@{/homeconnect/asset/favicon/safari-pinned-tab.svg}" color="#5bbad5">
<link rel="shortcut icon" th:href="@{/homeconnect/asset/favicon/favicon.ico}">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" th:content="@{/homeconnect/asset/favicon/browserconfig.xml}" content="browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- Home Connect binding CSS -->
<link th:href="@{/homeconnect/asset/css/homeconnect.css}" rel="stylesheet">
</head>
<body>
<nav th:fragment="topNav" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Home Connect Console</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid">
<div class="row">
<nav th:fragment="sidebarMenu (current)" th:assert="${!#strings.isEmpty(current)}" id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" th:href="@{/homeconnect}" href="#" th:classappend="${current == 'bridges' ? 'active' : ''}">
<span data-feather="link-2"></span>
Bridges
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/homeconnect/appliances}" href="#" th:classappend="${current == 'appliances' ? 'active' : ''}">
<span data-feather="grid"></span>
Appliances
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Reports</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" th:href="@{/homeconnect/log/requests}" href="#" th:classappend="${current == 'log-requests' ? 'active' : ''}">
<span data-feather="file-text"></span>
API Requests
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/homeconnect/log/events}" href="#" th:classappend="${current == 'log-events' ? 'active' : ''}">
<span data-feather="file-text"></span>
Events (SSE)
</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
</main>
</div>
</div>
<th:block th:fragment="js">
<script th:src="@{/homeconnect/asset/js/jquery-3.5.1.min.js}"></script>
<script th:src="@{/homeconnect/asset/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/homeconnect/asset/js/feather.min.js}"></script>
<script th:src="@{/homeconnect/asset/js/homeconnect.js}"></script>
</th:block>
</body>
</html>

View File

@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="base :: head"></head>
<body>
<nav th:replace="base :: topNav"></nav>
<div class="container-fluid">
<div class="row">
<nav th:replace="base :: sidebarMenu (current='bridges')"></nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Bridges</h1>
</div>
<div th:if="${bridgeHandlers.size() == 0}">No Home Connect bridge found. Please manually add 'Home Connect API' bridge and authorize it here.</div>
<div th:each="bridgeHandler: ${bridgeHandlers}">
<div class="card mb-3" th:with="thing=${bridgeHandler.getThing()}, status=${bridgeHandler.getThing().getStatus().toString()}, uid=${thing.getUID().getAsString()}">
<div class="card-body">
<section th:id="${uid}">
<h5 class="card-title" th:text="${thing.getLabel()}">Card title</h5>
</section>
<h6 class="card-subtitle mb-2 text-muted" th:text="${uid}">Card subtitle</h6>
<div th:if="${action == uid + 'clearCredentials'}" class="alert alert-success" role="alert">
Successfully cleared credentials.
</div>
<div th:if="${action == uid + 'authorize'}" class="alert alert-success" role="alert">
Successfully authorized bridge.
</div>
<dl class="row">
<dt class="col-sm-2">OAuth Flow</dt>
<dd class="col-sm-8">Authorization Code Grant Flow</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Client ID</dt>
<dd class="col-sm-8" th:text="${bridgeHandler.getConfiguration().getClientId()}">1234567890</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Client Secret</dt>
<dd class="col-sm-8" th:text="${bridgeHandler.getConfiguration().getClientSecret()}">xyz</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Redirect URI</dt>
<dd class="col-sm-8">
<span class="redirectUri">http://xy/homeconnect</span><br />
<small class="text-muted">Please make sure the redirect URI is matching the one specified at https://developer.home-connect.com/applications/.</small>
</dd>
<div class="w-100"></div>
<dt class="col-sm-2">API Endpoint</dt>
<dd class="col-sm-8" th:text="${bridgeHandler.getConfiguration().isSimulator() ? @org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants@API_SIMULATOR_BASE_URL : @org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants@API_BASE_URL}">xyz</dd>
<div class="w-100"></div>
<dt class="col-sm-2">SSE connections</dt>
<dd class="col-sm-8" th:text="${bridgeHandler.getEventSourceClient().connectionSize()}">xyz</dd>
<div class="w-100"></div>
<dt class="col-sm-2">Status</dt>
<dd class="col-sm-8">
<span class="badge" th:classappend="|${status == 'UNINITIALIZED' ? 'badge-warning' : ''} ${status == 'OFFLINE' ? 'badge-danger' : ''} ${status == 'ONLINE' ? 'badge-success' : ''}|" th:text="${status}">OFFLINE</span>
</dd>
<div class="w-100"></div>
<dt class="col-sm-2"></dt>
<dd class="col-sm-8">
<form method="post" style="display: inline-block">
<input type="hidden" name="bridgeId" th:value="${uid}"/>
<input type="hidden" name="action" value="clearCredentials">
<button type="submit" class="btn btn-secondary btn-sm">Clear stored credentials</button>
</form>
<form method="post" style="display: inline-block">
<input type="hidden" name="bridgeId" th:value="${uid}"/>
<input type="hidden" name="action" value="authorize">
<button type="submit" class="btn btn-primary btn-sm">Authorize bridge</button>
</form>
</dd>
</dl>
</div>
</div>
</div>
</main>
</div>
</div>
<!--/*/ <th:block th:include="base :: js">
</th:block> /*/-->
</body>
</html>

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="base :: head"></head>
<body>
<nav th:replace="base :: topNav"></nav>
<div class="container-fluid">
<div class="row">
<nav th:replace="base :: sidebarMenu (current='log-events')"></nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Events</h1>
</div>
<div th:if="${bridgeHandlers.size() == 0}" class="mb-4"><span class="text-muted">No events</span></div>
<div class="mb-4" th:each="bridgeHandler: ${bridgeHandlers}" th:with="thing=${bridgeHandler.getThing()}, queue=${bridgeHandler.getEventSourceClient().getLatestEvents()}, status=${bridgeHandler.getThing().getStatus().toString()}, uid=${bridgeHandler.getThing().getUID().getAsString()}">
<h2 th:text="${thing.getLabel() + ' (' + uid + ')'}" style="display: inline-block;">Section title</h2>
<div class="btn-toolbar float-right">
<button type="button" class="btn btn-sm btn-outline-secondary reload-page mr-2"><span data-feather="refresh-cw"></span></button>
<a th:href="@{/homeconnect/log/events(export='json',bridgeId=${uid})}" class="btn btn-sm btn-outline-secondary">Export</a>
</div>
<div th:if="${queue.size() == 0}"><span class="text-muted">No events</span></div>
<div th:if="${queue.size() > 0}" class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>Timestamp</th>
<th>HaId</th>
<th>Type</th>
<th>Level</th>
<th>Handling</th>
<th>URI</th>
<th>Name</th>
<th>Value</th>
<th>Unit</th>
</tr>
</thead>
<tbody>
<tr th:each="entry: ${queue}">
<td th:text="${#temporals.format(entry.creation, 'yyyy-MM-dd HH:mm:ss.SSS Z')}">27.04.2020 14:30</td>
<td><span th:text="${entry.haId}" class="badge badge-light">align-middle</span></td>
<td><span th:text="${entry.type}" class="badge badge-secondary"></span></td>
<td><span th:if="${entry.level}" th:text="${entry.level}" class="badge" th:classappend="|${entry.level.getLevel() == 'critical' || entry.level.getLevel() == 'alert' ? 'badge-danger' : ''} ${entry.level.getLevel() == 'info' || entry.level.getLevel() == 'hint' ? 'badge-success' : ''} ${entry.level.getLevel() == 'warning' ? 'badge-warning' : ''}|">hint</span></td>
<td><span class="badge badge-light" th:text="${entry.handling}">none</span></td>
<td class="text-break" th:text="${entry.uri}">/api/homeappliances/BOSCH-HNG6764B6-0000000011FF/programs/active/options/Cooking.Oven.Option.SetpointTemperature</td>
<td th:text="${entry.name}">Target temperature for the oven</td>
<td th:text="${entry.value}">200</td>
<td th:text="${entry.unit}">°C</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
<!--/*/ <th:block th:include="base :: js">
</th:block> /*/-->
</body>
</html>

View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="base :: head"></head>
<body>
<nav th:replace="base :: topNav"></nav>
<div class="container-fluid">
<div class="row">
<nav th:replace="base :: sidebarMenu (current='log-requests')"></nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">API Requests</h1>
</div>
<div th:if="${bridgeHandlers.size() == 0}" class="mb-4"><span class="text-muted">No requests</span></div>
<div class="mb-4" th:each="bridgeHandler: ${bridgeHandlers}" th:with="thing=${bridgeHandler.getThing()}, queue=${bridgeHandler.getApiClient().getLatestApiRequests()}, status=${bridgeHandler.getThing().getStatus().toString()}, uid=${bridgeHandler.getThing().getUID().getAsString()}">
<h2 th:text="${thing.getLabel() + ' (' + uid + ')'}" style="display: inline-block;">Section title</h2>
<div class="btn-toolbar float-right">
<button type="button" class="btn btn-sm btn-outline-secondary reload-page mr-2"><span data-feather="refresh-cw"></span></button>
<a th:href="@{/homeconnect/log/requests(export='json',bridgeId=${uid})}" class="btn btn-sm btn-outline-secondary">Export</a>
</div>
<div th:if="${queue.size() == 0}"><span class="text-muted">No requests</span></div>
<div th:unless="${queue.size() == 0}" class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>Request Time</th>
<th class="text-center">Response Code</th>
<th class="text-center">Method</th>
<th>URL</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="entry: ${queue}">
<td th:text="${#temporals.format(entry.time, 'yyyy-MM-dd HH:mm:ss.SSS Z')}">27.04.2020 14:30</td>
<td class="text-center"><span th:if="${entry.response}" class="badge" th:classappend="|${entry.response.code >= 300 && entry.response.code != 404 ? 'badge-danger' : ''} ${entry.response.code >=200 && entry.response.code < 300 ? 'badge-success' : ''} ${entry.response.code == 404 ? 'badge-warning' : ''}|" th:text="${entry.response.code}">200</span></td>
<td class="text-center" th:text="${entry.request.method}">GET</td>
<td th:text="${entry.request.getShortUrl()}">/bla/bla?w92374=34</td>
<td class="text-center">
<button type="button" class="btn btn-info btn-sm py-0" style="font-size: 0.8em;" th:attr="data-request-id=${entry.id}" data-toggle="modal" data-target="#requestDetailModal">
Details
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- Request detail modal -->
<div class="modal fade" id="requestDetailModal" tabindex="-1" role="dialog" aria-labelledby="requestDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title .text-truncate" id="requestDetailModalLabel">Request Details</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5>Request Body</h5>
<pre class="modal-request-body"></pre>
<hr>
<h5>Response Body</h5>
<pre class="modal-response-body"></pre>
<hr>
<h5>Response Header</h5>
<dl class="row modal-response-header"></dl>
<hr>
<h5>Request Header</h5>
<dl class="row modal-request-header"></dl>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" th:inline="javascript">
var requests = JSON.parse(/*[[${requests}]]*/);
</script>
</div>
<!--/*/ <th:block th:include="base :: js">
</th:block> /*/-->
</body>
</html>