Skip to content

Commit 4577911

Browse files
Merge pull request #126 from stefanhaustein/master
Add a simple HomeAssitant friendly REST server example.
2 parents 3e23169 + ab34552 commit 4577911

2 files changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
A simple REST server that reports the measurements for all
2+
auto-detected I2C sensors as a simple JSON object.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package com.pi4j.examples.server;
2+
3+
import com.pi4j.Pi4J;
4+
import com.pi4j.context.Context;
5+
import com.pi4j.drivers.sensor.Sensor;
6+
import com.pi4j.drivers.sensor.SensorDescriptor;
7+
import com.pi4j.drivers.sensor.SensorDetector;
8+
9+
import java.io.*;
10+
import java.net.InetSocketAddress;
11+
import java.net.ServerSocket;
12+
import java.net.Socket;
13+
import java.util.*;
14+
15+
public class SensorServer {
16+
private static final int BUS = 1;
17+
private static final int PORT = 8147;
18+
19+
private final ServerSocket serverSocket;
20+
private final List<Sensor> sensorList;
21+
private final String address;
22+
23+
SensorServer(int port, List<Sensor> sensorList) throws IOException {
24+
serverSocket = new ServerSocket(port);
25+
this.sensorList = sensorList;
26+
try (
27+
Socket socket = new Socket();
28+
) {
29+
// Get our own address, see https://stackoverflow.com/a/41822127/1401879
30+
socket.connect(new InetSocketAddress("google.com", 80));
31+
address = "http:/" + socket.getLocalAddress() + ":" + port;
32+
}
33+
}
34+
35+
void run() {
36+
while(true) {
37+
try(Socket socket = serverSocket.accept()) {
38+
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
39+
String request = in.readLine();
40+
System.out.println(new Date() + ": Request from " + socket.getRemoteSocketAddress() + ": " + request);
41+
while (true) {
42+
String line = in.readLine();
43+
if (line == null || line.isEmpty()) {
44+
break;
45+
}
46+
}
47+
Writer writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
48+
if (request == null || !request.startsWith("GET /")) {
49+
writer.write("HTTP/1.0 501 Error\r\n\r\n");
50+
} else {
51+
int end = request.lastIndexOf(' ');
52+
String path = request.substring(5, end);
53+
StringBuilder builder = new StringBuilder();
54+
String contentType = switch (path) {
55+
case "" -> renderHelp(builder);
56+
case "measurements" -> renderData(builder);
57+
case "config" -> renderConfig(builder);
58+
default -> null;
59+
};
60+
61+
if (contentType == null) {
62+
writer.write("HTTP/1.0 404 Not Found\r\n\r\n");
63+
} else {
64+
writer.write("HTTP/1.0 200 OK\r\n");
65+
writer.write("Content-Type: " + contentType + "; charset=utf-8\r\n\r\n");
66+
writer.write(builder.toString());
67+
}
68+
}
69+
writer.close();
70+
} catch (IOException e) {
71+
e.printStackTrace();
72+
}
73+
}
74+
}
75+
76+
String renderData(StringBuilder sb) {
77+
boolean first = true;
78+
sb.append("{");
79+
Map<String, Integer> names = new HashMap<>();
80+
for (Sensor sensor : sensorList) {
81+
SensorDescriptor descriptor = sensor.getDescriptor();
82+
List<SensorDescriptor.Value> valueDescriptors = descriptor.getValues();
83+
float[] measurements = new float[valueDescriptors.size()];
84+
sensor.readMeasurement(measurements);
85+
for (SensorDescriptor.Value valueDescriptor : valueDescriptors) {
86+
sb.append(first ? "\n " : ",\n ");
87+
first = false;
88+
sb.append('"');
89+
sb.append(getUniqueName(names, valueDescriptor));
90+
sb.append("\": ");
91+
sb.append(measurements[valueDescriptor.getIndex()]);
92+
}
93+
}
94+
sb.append("\n}\n");
95+
return "application/json";
96+
}
97+
98+
String renderConfig(StringBuilder sb) {
99+
// https://www.home-assistant.io/integrations/sensor.rest/
100+
sb.append("# Add this snippet to the 'rest:' section in your HomeAssistant config.yaml:\n");
101+
sb.append("# rest:\n");
102+
sb.append(" - resource: \"").append(address).append("/measurements\"\n");
103+
sb.append(" sensor:\n");
104+
Map<String, Integer> names = new HashMap<>();
105+
for (Sensor sensor : sensorList) {
106+
SensorDescriptor descriptor = sensor.getDescriptor();
107+
List<SensorDescriptor.Value> valueDescriptors = descriptor.getValues();
108+
float[] measurements = new float[valueDescriptors.size()];
109+
sensor.readMeasurement(measurements);
110+
for (SensorDescriptor.Value valueDescriptor : valueDescriptors) {
111+
String name = getUniqueName(names, valueDescriptor);
112+
String deviceClass = getDeviceClass(valueDescriptor.getKind());
113+
if (deviceClass != null) {
114+
sb.append(" - name: \"").append(name).append("\"\n");
115+
sb.append(" value_template: \"{{ value_json['").append(name).append("'] }}\"\n");
116+
sb.append(" device_class: ").append(deviceClass).append("\n");
117+
sb.append(" unit_of_measurement: \"").append(getUnit(valueDescriptor.getKind())).append("\"\n");
118+
}
119+
}
120+
}
121+
return "text/vnd.yaml";
122+
}
123+
124+
String renderHelp(StringBuilder sb) {
125+
sb.append("Pi4J I2C Autodetect Sensor Server\n\n");
126+
127+
sb.append("HomeAssistant YAML\n");
128+
sb.append("configuration snippet: ").append(address).append("/config\n");
129+
sb.append("Measurements: ").append(address).append("/measurements\n");
130+
sb.append("This help text: ").append(address).append("\n");
131+
132+
return "text/plain";
133+
}
134+
135+
static String getDeviceClass(SensorDescriptor.Kind kind) {
136+
return switch(kind) {
137+
case LIGHT, LIGHT_BLUE, LIGHT_RED, LIGHT_GREEN -> "illuminance";
138+
case CO2, DISTANCE, HUMIDITY, PRESSURE, TEMPERATURE -> kind.name().toLowerCase(Locale.ROOT);
139+
default -> null;
140+
};
141+
}
142+
143+
static String getUnit(SensorDescriptor.Kind kind) {
144+
return switch (kind) {
145+
case ACCELERATION_X, ACCELERATION_Y, ACCELERATION_Z -> "m/s²";
146+
case ANGULAR_VELOCITY_X, ANGULAR_VELOCITY_Y, ANGULAR_VELOCITY_Z -> "°/s";
147+
case CO2 -> "ppm";
148+
case DISTANCE -> "m";
149+
case HUMIDITY -> "%";
150+
case LIGHT, LIGHT_BLUE, LIGHT_RED, LIGHT_GREEN -> "lx";
151+
case MAGNETIC_FIELD_X, MAGNETIC_FIELD_Y, MAGNETIC_FIELD_Z -> "G";
152+
case PRESSURE -> "hPa";
153+
case TEMPERATURE -> "°C";
154+
};
155+
}
156+
157+
static String getUniqueName(Map<String, Integer> names, SensorDescriptor.Value valueDescriptor) {
158+
String name = valueDescriptor.getKind().toString().toLowerCase(Locale.ROOT);
159+
int suffix = names.getOrDefault(name, 1);
160+
names.put(name, suffix + 1);
161+
return suffix > 1 ? name + "_" + suffix : name;
162+
}
163+
164+
public static void main(String[] args) throws InterruptedException, IOException {
165+
Context pi4j = Pi4J.newAutoContext();
166+
List<Sensor> sensorList = SensorDetector.detectI2cSensors(pi4j, BUS);
167+
System.out.println("Detected sensors: " + sensorList.stream().map(s -> s.getClass().getSimpleName()).toList());
168+
System.out.println("Starting server on port " + PORT);
169+
System.out.println();
170+
SensorServer server = new SensorServer(PORT, sensorList);
171+
172+
StringBuilder sb = new StringBuilder();
173+
server.renderHelp(sb);
174+
System.out.println(sb);
175+
176+
server.run();
177+
}
178+
}

0 commit comments

Comments
 (0)