After Flashear Esp8266, fucK AT-commands it would be interesting to give it another use than just turning on or off a simple led.
How about plotting temperature and humidity with it? as well as any other sensor that reacts to these environmental changes.
We do need him to run the next code. to enable the enpoints that we will consume with Spring WebClient WebFlux (Spring’s reactive client built on top of project reactor and reactor-netty).
The animation following shows a Grid and a Chart where our two sensors are plotted in real time, it is time to give credits to @Detlef Boehm for the graphing tips.
This code is javascript formatted in java, it will be executed on the client side, necessary to give a resolution of 1 second to the graph, without it does not work, and it does not format the String (the time that appears at the bottom of the graph).
private static final String FORMATTER_TIMESTAMP = "function (value, timestamp) {\n" +
" var date = new Date(timestamp);" +
" var hours = date.getHours();\n" +
" var minutes = date.getMinutes();\n" +
" var seconds = date.getSeconds();" +
" var ampm = hours >= 12 ? "pm" : "am";\n" +
" hours = hours % 12;\n" +
" hours = hours ? hours : 12; // the hour "0" should be "12"\n" +
" minutes = minutes < 10 ? "0"+minutes : minutes;\n" +
" return hours + ":" + minutes + ":" + ("0"+seconds).slice(-2) + " " + ampm;\n" +
"}";
Connector configuration reactor-netty
With WebClient we have a default configuration with the base URL that we use in two places, one to consume the sensor endpoint and the other to turn on the LED.
WebClient internally uses a default connection with netty reactor, so when we have no response from our micro, the error is displayed to the client after waiting 30 seconds. Info - Response-timeout. |
To notify the client faster (after 2 seconds) our bean will look like this:
@Bean
public WebClient sensorWebClientBuilder(final WebClient.Builder webClient) {
HttpClient httpClient = HttpClient.create() (1)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) (2)
.doOnConnected((Connection connection) ->
(3)
connection.addHandlerLast(new ReadTimeoutHandler(2000, TimeUnit.MILLISECONDS)));
return webClient
.clientConnector(new ReactorClientHttpConnector(httpClient)) (4)
.baseUrl(BASE_URL)
.build();
}
1 | An instance of the netty reactor client is configured |
2 | Timeout of 2 seconds |
3 | ReadTimeout with 2 seconds minimum. |
4 | The netty reactor client is set in the WebClient . |
The above configuration could also be performed via properties.
Sensor endpoint
this.webClient
.get()
.uri(DHT_22)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(SensorDht22Mapper.class)
.delaySubscription(Duration.ofSeconds(2)) (1)
.repeat()
.doOnError((Throwable throwable) -> { (2)
log.warn("Error {}", throwable);
ui.access(() -> this.showError(throwable.getMessage()));
})
.retryWhen(Retry.indefinitely()); (3)
1 | Yes, we must delay for 2 seconds and repeat the subscription to obtain the updated data. |
2 | In case of error notify the user thanks to the use of operator doOnError . |
3 | We resubscribe with retryWhen indefinitely in case of error, interesting could also be the use of Resilence4j which is known to be a more advanced api for high availability. |
Led endpoint
this.webClient
.put()
.uri(functionQueryParameters) (1)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(SensorDht22.class)
.doOnError(error -> ui.access(() -> Notification.show(error.getMessage()))) (2)
.subscribe(sensorDht22 -> {
ui.access(() -> {
Notification.show(sensorDht22.getStatus());
});
});
1 | This is the function that allows us to create the query by means of a builder, we make a put with the necessary query-parameters ?id=2&status={on/off} . |
2 | If there is an error, we notify the customer. |
Main view of the sensors 🔥
Notify if the bug goes down.
Simulating that the microphone is not available, we do it by removing the voltage and after 2 seconds without response, the backend from Vaadin will notify the client.
Using bulkhead
Depending on the server used, you must also customize the thread management, either tomcat, netty etc., so that the thread control is performed well in conjunction with this pattern. |
The bulkhead pattern is quite useful to control the maximum number of concurrent calls at the same time.
Closely related to Little’s law, is not only about isolating the thread-pool, but it is more about controlling the consumption and resource consumption.
L = λ * W
-
L
The average number of simultaneous tasks in a queuing system. -
λ
The average number of tasks arriving at a queuing system per unit time. -
W
The average service time a task spends in a queuing system.
Dependency on maven
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
<version>1.7.1</version>
</dependency>
@Bean
public Bulkhead getBulkHeadConfig() {
return Bulkhead.of("bulk-head", BulkheadConfig
.custom()
.writableStackTraceEnabled(true)
.maxConcurrentCalls(5) (1)
.maxWaitDuration(Duration.ofMillis(5))
.build());
}
1 | Maximum concurrent calls can also be calculated based on system processors and divided by 2, Runtime.getRuntime().availableProcessors() / 2; |
final Flux<SensorDht22Mapper> sensorResponseMapper = this.webClient
.get()
.uri(DHT_22)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(SensorDht22Mapper.class)
.delaySubscription(Duration.ofSeconds(2))
.repeat()
.transform(BulkheadOperator.of(this.bulkhead)) (1)
.subscribeOn(Schedulers.immediate())
.publishOn(Schedulers.immediate())
.doOnError((Throwable throwable) -> {
log.warn("Error {}", throwable);
ui.access(() -> this.showError("ESP8266 not found!"));
})
.retryWhen(Retry.indefinitely()); (2)
1 | Use of the transform operator that allows to act on this publisher and customize it, acting at assembly time, allowing to work with the BulkheadOperator operator. |
2 | This retry may not be the best way to do it, possibly a touch up is needed, but not bad. |
-
Exporting the grid data to other formats is pending, pdf-excell-txt-csv etc…