Skip to content

Commit 112ecc8

Browse files
author
Niels van de Weem
committed
Implement HA Availability
1 parent a93163f commit 112ecc8

File tree

7 files changed

+199
-124
lines changed

7 files changed

+199
-124
lines changed

src/main/java/com/getpcpanel/mqtt/MqttDeviceService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private void initialize(Device device) {
119119
writeLighting(device, lighting);
120120
buildSubscriptions(device, lighting);
121121

122-
if (saveService.get().getMqtt().homeAssistantDiscovery()) {
122+
if (saveService.get().getMqtt().homeAssistant().enableDiscovery()) {
123123
mqttHomeAssistantHelper.discover(saveService.get().getMqtt(), device);
124124
}
125125
}

src/main/java/com/getpcpanel/mqtt/MqttHomeAssistantHelper.java

+86-50
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44

5+
import javax.annotation.Nonnull;
56
import javax.annotation.Nullable;
67

78
import org.apache.commons.lang3.StringUtils;
@@ -23,50 +24,44 @@ public class MqttHomeAssistantHelper {
2324
@Value("${application.version}") private String version;
2425

2526
public void clearAll(MqttSettings settings) {
26-
var topic = StringUtils.joinWith("/", settings.homeAssistantBaseTopic(), "+", "pcpanel", "#");
27+
var topic = StringUtils.joinWith("/", settings.homeAssistant().baseTopic(), "+", "pcpanel", "#");
2728
mqttService.removeAll(topic);
2829
}
2930

3031
public void discover(MqttSettings settings, Device device) {
31-
var haDevice = new HomeAssistantDevice(
32-
version,
33-
List.of(device.getSerialNumber()),
34-
"PCPanel Holdings, LLC",
35-
device.getDeviceType().getNiceName(),
36-
device.getSerialNumber(),
37-
device.getSerialNumber()
38-
);
32+
var haDevice = buildDevice(device);
33+
var availability = buildAvailability(settings);
3934

40-
addAnalogValueConfigs(settings, device, haDevice);
41-
addBrightnessDevice(settings, device, haDevice);
42-
addLights(settings, device, haDevice);
43-
addButtons(settings, device, haDevice);
35+
addAnalogValueConfigs(settings, device, haDevice, availability);
36+
addBrightnessDevice(settings, device, haDevice, availability);
37+
addLights(settings, device, haDevice, availability);
38+
addButtons(settings, device, haDevice, availability);
4439
}
4540

46-
private void addLights(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
47-
addLogoLight(settings, device, haDevice);
48-
addControlLights(settings, device, haDevice);
41+
private void addLights(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
42+
addLogoLight(settings, device, haDevice, availability);
43+
addControlLights(settings, device, haDevice, availability);
4944
}
5045

51-
private void addControlLights(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
46+
private void addControlLights(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
5247
for (var i = 0; i < device.getDeviceType().getAnalogCount(); i++) {
5348
var buttonCount = device.getDeviceType().getButtonCount();
5449
var type = i < buttonCount ? MqttTopicHelper.ColorType.knob : MqttTopicHelper.ColorType.slider;
5550
var idx = i < buttonCount ? i : i - buttonCount;
5651

57-
addControlLightConfig(settings, device, haDevice, i, type, idx);
52+
addControlLightConfig(settings, device, haDevice, availability, i, type, idx);
5853
if (type == MqttTopicHelper.ColorType.slider) {
59-
addSliderLabelLightConfig(settings, device, haDevice, i, idx, type);
54+
addSliderLabelLightConfig(settings, device, haDevice, availability, idx, type);
6055
}
6156
}
6257
}
6358

64-
private void addControlLightConfig(MqttSettings settings, Device device, HomeAssistantDevice haDevice, int i, MqttTopicHelper.ColorType type, int idx) {
59+
private void addControlLightConfig(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability, int i, MqttTopicHelper.ColorType type, int idx) {
6560
var controlConfigTopic = lightTopicFor(settings, device, "control_" + i);
6661
var controlValueTopic = topicHelper.lightTopic(device.getSerialNumber(), type, idx);
6762

6863
var config = new HomeAssistantLightConfig(
69-
haDevice,
64+
haDevice, availability,
7065
StringUtils.capitalize(type.name()) + " " + (idx + 1) + " Light",
7166
device.getSerialNumber() + "_" + type.name() + "_" + idx,
7267
controlValueTopic,
@@ -75,12 +70,12 @@ private void addControlLightConfig(MqttSettings settings, Device device, HomeAss
7570
mqttService.send(controlConfigTopic, config, false);
7671
}
7772

78-
private void addSliderLabelLightConfig(MqttSettings settings, Device device, HomeAssistantDevice haDevice, int i, int idx, MqttTopicHelper.ColorType type) {
73+
private void addSliderLabelLightConfig(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability, int idx, MqttTopicHelper.ColorType type) {
7974
var labelConfigTopic = lightTopicFor(settings, device, "label_" + idx);
8075
var labelValueTopic = topicHelper.lightTopic(device.getSerialNumber(), MqttTopicHelper.ColorType.label, idx);
8176

8277
var labelConfig = new HomeAssistantLightConfig(
83-
haDevice,
78+
haDevice, availability,
8479
StringUtils.capitalize(type.name()) + " " + (idx + 1) + " Label Light",
8580
device.getSerialNumber() + "_label_" + idx,
8681
labelValueTopic,
@@ -89,49 +84,41 @@ private void addSliderLabelLightConfig(MqttSettings settings, Device device, Hom
8984
mqttService.send(labelConfigTopic, labelConfig, false);
9085
}
9186

92-
private void addAnalogValueConfigs(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
87+
private void addAnalogValueConfigs(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
9388
for (var i = 0; i < device.getDeviceType().getAnalogCount(); i++) {
9489
var configTopic = configTopicFor(settings, device, "number", "analog", i);
9590
var valueTopic = topicHelper.valueTopic(device.getSerialNumber(), MqttTopicHelper.ValueType.analog, i);
9691

9792
var config = new HomeAssistantNumberConfig(
98-
haDevice,
93+
haDevice, availability,
9994
determineAnalogName(device, i),
10095
valueTopic,
10196
valueTopic,
102-
null,
10397
determineAnalogIcon(device, i),
104-
0,
10598
255,
106-
"slider",
107-
"analog_" + i,
108-
device.getSerialNumber() + "_analog_" + i,
109-
true);
99+
device.getSerialNumber() + "_analog_" + i
100+
);
110101
mqttService.send(configTopic, config, false);
111102
}
112103
}
113104

114-
private void addBrightnessDevice(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
105+
private void addBrightnessDevice(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
115106
var configTopic = configTopicFor(settings, device, "number", "brightness", 0);
116107
var valueTopic = topicHelper.valueTopic(device.getSerialNumber(), MqttTopicHelper.ValueType.brightness, 0);
117108

118109
var config = new HomeAssistantNumberConfig(
119-
haDevice,
110+
haDevice, availability,
120111
"Brightness",
121112
valueTopic,
122113
valueTopic,
123-
null,
124114
"mdi:brightness-percent",
125-
0,
126115
100,
127-
"slider",
128-
"brightness",
129-
device.getSerialNumber() + "_brightness",
130-
true);
116+
device.getSerialNumber() + "_brightness"
117+
);
131118
mqttService.send(configTopic, config, false);
132119
}
133120

134-
private void addLogoLight(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
121+
private void addLogoLight(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
135122
if (!device.getDeviceType().isHasLogoLed()) {
136123
return;
137124
}
@@ -140,7 +127,7 @@ private void addLogoLight(MqttSettings settings, Device device, HomeAssistantDev
140127
var valueTopic = topicHelper.lightTopic(device.getSerialNumber(), MqttTopicHelper.ColorType.logo, 0);
141128

142129
var config = new HomeAssistantLightConfig(
143-
haDevice,
130+
haDevice, availability,
144131
"Logo Light",
145132
device.getSerialNumber() + "_logo",
146133
valueTopic,
@@ -149,13 +136,13 @@ private void addLogoLight(MqttSettings settings, Device device, HomeAssistantDev
149136
mqttService.send(configTopic, config, false);
150137
}
151138

152-
private void addButtons(MqttSettings settings, Device device, HomeAssistantDevice haDevice) {
139+
private void addButtons(MqttSettings settings, Device device, HomeAssistantDevice haDevice, @Nullable HomeAssistantAvailability availability) {
153140
for (var i = 0; i < device.getDeviceType().getButtonCount(); i++) {
154141
var configTopic = configTopicFor(settings, device, "binary_sensor", "button", i);
155142
var valueTopic = topicHelper.actionTopic(device.getSerialNumber(), MqttTopicHelper.ActionType.button, i);
156143

157144
var config = new HomeAssistantButtonConfig(
158-
haDevice,
145+
haDevice, availability,
159146
valueTopic,
160147
"Button " + (i + 1),
161148
device.getSerialNumber() + "_button_" + i
@@ -166,7 +153,7 @@ private void addButtons(MqttSettings settings, Device device, HomeAssistantDevic
166153

167154
private String configTopicFor(MqttSettings settings, Device device, String domain, String type, int idx) {
168155
return StringUtils.joinWith("/",
169-
settings.homeAssistantBaseTopic(),
156+
settings.homeAssistant().baseTopic(),
170157
domain,
171158
"pcpanel",
172159
device.getSerialNumber().toLowerCase() + "_" + type + "_" + idx,
@@ -176,7 +163,7 @@ private String configTopicFor(MqttSettings settings, Device device, String domai
176163

177164
private String lightTopicFor(MqttSettings settings, Device device, String name) {
178165
return StringUtils.joinWith("/",
179-
settings.homeAssistantBaseTopic(),
166+
settings.homeAssistant().baseTopic(),
180167
"light",
181168
"pcpanel",
182169
device.getSerialNumber().toLowerCase() + "_" + name,
@@ -202,10 +189,10 @@ private String determineAnalogName(Device device, int i) {
202189

203190
record HomeAssistantNumberConfig(
204191
HomeAssistantDevice device,
192+
@Nullable HomeAssistantAvailability availability,
205193
String name,
206194
String command_topic,
207195
String state_topic,
208-
@Nullable String device_class,
209196
String icon,
210197
int min, // 0
211198
int max, // 255
@@ -214,10 +201,26 @@ record HomeAssistantNumberConfig(
214201
String unique_id,
215202
boolean retain // true
216203
) {
204+
HomeAssistantNumberConfig(HomeAssistantDevice device, @Nullable HomeAssistantAvailability availability, String name, String command_topic, String state_topic, String icon, int max, String object_id) {
205+
this(
206+
device, availability,
207+
name,
208+
command_topic,
209+
state_topic,
210+
icon,
211+
0,
212+
max,
213+
"slider",
214+
object_id,
215+
object_id,
216+
true
217+
);
218+
}
217219
}
218220

219221
record HomeAssistantLightConfig(
220222
HomeAssistantDevice device,
223+
@Nullable HomeAssistantAvailability availability,
221224
String name,
222225
String object_id,
223226
String unique_id,
@@ -236,8 +239,8 @@ record HomeAssistantLightConfig(
236239
List<String> supported_color_modes,
237240
boolean retain
238241
) {
239-
HomeAssistantLightConfig(HomeAssistantDevice device, String name, String unique_id, String command_topic, String icon) {
240-
this(device, name, unique_id, unique_id, command_topic, icon,
242+
HomeAssistantLightConfig(HomeAssistantDevice device, @Nullable HomeAssistantAvailability availability, String name, String unique_id, String command_topic, String icon) {
243+
this(device, availability, name, unique_id, unique_id, command_topic, icon,
241244
"template",
242245
"#000000",
243246
"#{{ '%02x%02x%02x' | format(" + asInt("red") + ", " + asInt("green") + ", " + asInt("blue") + ") }}",
@@ -258,6 +261,7 @@ private static String asInt(String name) {
258261

259262
record HomeAssistantButtonConfig( // Is actually a binary sensor
260263
HomeAssistantDevice device,
264+
@Nullable HomeAssistantAvailability availability,
261265
String state_topic,
262266
String name,
263267
String object_id,
@@ -268,15 +272,39 @@ record HomeAssistantButtonConfig( // Is actually a binary sensor
268272
String payload_off,
269273
String payload_on
270274
) {
271-
HomeAssistantButtonConfig(HomeAssistantDevice device, String command_topic, String name, String object_id) {
272-
this(device, command_topic, name, object_id, object_id,
275+
HomeAssistantButtonConfig(HomeAssistantDevice device, @Nullable HomeAssistantAvailability availability, String command_topic, String name, String object_id) {
276+
this(device, availability, command_topic, name, object_id, object_id,
273277
"click",
274278
"mdi:toggle-switch",
275279
"release",
276280
"click");
277281
}
278282
}
279283

284+
@Nonnull
285+
private HomeAssistantDevice buildDevice(Device device) {
286+
return new HomeAssistantDevice(
287+
version,
288+
List.of(device.getSerialNumber()),
289+
"PCPanel Holdings, LLC",
290+
device.getDeviceType().getNiceName(),
291+
device.getSerialNumber(),
292+
device.getSerialNumber()
293+
);
294+
}
295+
296+
private @Nullable HomeAssistantAvailability buildAvailability(MqttSettings settings) {
297+
if (settings.homeAssistant().availability()) {
298+
return new HomeAssistantAvailability(
299+
topicHelper.availabilityTopic(),
300+
"online",
301+
"offline",
302+
"{{'offline' if (value is undefined or value != 'online') else 'online'}}"
303+
);
304+
}
305+
return null;
306+
}
307+
280308
record HomeAssistantDevice(
281309
String sw_version,
282310
List<String> identifiers,
@@ -286,4 +314,12 @@ record HomeAssistantDevice(
286314
String serial_number
287315
) {
288316
}
317+
318+
record HomeAssistantAvailability(
319+
String topic,
320+
String payload_available,
321+
String payload_not_available,
322+
String value_template
323+
) {
324+
}
289325
}

src/main/java/com/getpcpanel/mqtt/MqttService.java

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class MqttService {
3737
private final ApplicationEventPublisher eventPublisher;
3838
private final ObjectMapper objectMapper;
3939
private final Debouncer debouncer;
40+
private final MqttTopicHelper topicHelper;
4041
private MqttSettings connectedSettings;
4142
@Nullable private Mqtt5Client mqttClient;
4243

@@ -139,18 +140,21 @@ public void saveChanged() {
139140
}
140141

141142
private void connect(MqttSettings mqttSettings) {
143+
var availabilityTopic = topicHelper.availabilityTopic();
142144
var builder = MqttClient.builder()
143145
.identifier(UUID.randomUUID().toString())
144146
.serverHost(mqttSettings.host())
145147
.serverPort(mqttSettings.port())
146148
.useMqttVersion5()
147149
.automaticReconnectWithDefaultConfig()
150+
.willPublish().topic(availabilityTopic).payload("offline".getBytes()).applyWillPublish()
148151
.simpleAuth().username(mqttSettings.username()).password(mqttSettings.password().getBytes()).applySimpleAuth();
149152
if (mqttSettings.secure()) {
150153
builder = builder.sslWithDefaultConfig();
151154
}
152155
mqttClient = builder.build();
153156
mqttClient.toBlocking().connect();
157+
send(availabilityTopic, "online".getBytes(), true);
154158
log.info("Connected to MQTT server");
155159
}
156160

src/main/java/com/getpcpanel/mqtt/MqttTopicHelper.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.getpcpanel.mqtt;
22

3-
import org.apache.commons.lang3.StringUtils;
43
import org.springframework.stereotype.Service;
54

65
import com.getpcpanel.profile.MqttSettings;
76
import com.getpcpanel.profile.SaveService;
87

98
import lombok.RequiredArgsConstructor;
109
import lombok.extern.log4j.Log4j2;
10+
import one.util.streamex.StreamEx;
1111

1212
@Log4j2
1313
@Service
@@ -19,24 +19,28 @@ public DeviceMqttTopicHelper device(String deviceSerial) {
1919
return new DeviceMqttTopicHelper(deviceSerial);
2020
}
2121

22+
public String availabilityTopic() {
23+
return baseJoining("available");
24+
}
25+
2226
public String baseTopicFilter() {
23-
var mqttSettings = getSettings();
24-
return StringUtils.joinWith("/", mqttSettings.baseTopic(), "#");
27+
return baseJoining("#");
2528
}
2629

2730
public String valueTopic(String deviceSerial, ValueType type, int index) {
28-
var mqttSettings = getSettings();
29-
return StringUtils.joinWith("/", mqttSettings.baseTopic(), deviceSerial, "values", type.name() + index);
31+
return baseJoining(deviceSerial, "values", type.name() + index);
3032
}
3133

3234
public String actionTopic(String deviceSerial, ActionType type, int index) {
33-
var mqttSettings = getSettings();
34-
return StringUtils.joinWith("/", mqttSettings.baseTopic(), deviceSerial, "actions", type.name() + index);
35+
return baseJoining(deviceSerial, "actions", type.name() + index);
3536
}
3637

3738
public String lightTopic(String deviceSerial, ColorType type, int index) {
38-
var mqttSettings = getSettings();
39-
return StringUtils.joinWith("/", mqttSettings.baseTopic(), deviceSerial, "lighting", type.name(), index);
39+
return baseJoining(deviceSerial, "lighting", type.name(), index);
40+
}
41+
42+
private String baseJoining(Object... parts) {
43+
return StreamEx.of(parts).prepend(getSettings().baseTopic()).joining("/");
4044
}
4145

4246
private MqttSettings getSettings() {

0 commit comments

Comments
 (0)