SignalK / SensESP

Universal Signal K sensor framework for the ESP32 platform
https://signalk.org/SensESP/
Apache License 2.0
153 stars 81 forks source link

4 sensors cause error, plus SystemHz is wrong and depends on number of sensors #553

Closed nadrog closed 2 years ago

nadrog commented 2 years ago

I'm running SensESP 2.1.1 on a SailorHAT that is using ESP32-WROOM-32D. According to documentation, the ESP32 "clock frequency is adjustable from 80 MHz to 240 MHz", but when I enable_system_hz_sensor(), the values I receive depend on the number of RepeatSensors and can be as low as in range from 0 to 2. Additionally and more problematically, I'm not able to use more than 3 sensors.

I'm controlling the number of sensors by commenting the code like auto* bme680_temperature = new RepeatSensor<float>(read_interval, [](){return bme680.readTemperature() + 273.15;}); bme680_temperature->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature","","K"));

  1. without any sensors, the values for "sensorDevice.SailorHat.systemHz" are ~700 000
  2. with one sensor, the values are ~430k
  3. with two sensors, ~160k
  4. with three, values are 0, 1 or 2
  5. with four, I get error [E][WiFiClient.cpp:485] flush(): fail on fd 63, errno: 11, "No more processes" during attempt to initiate websocket which is after successful initial handshake with the server. SystemHz

I'm using Adafruit BME680 Library@^2.0.1 and signalk/SensESP@^2.1.1.

Debug output:

(connect)(C1) WSClient websocket connect attempt (state=0) (connect)(C1) Initiating websocket connection with server... (get_mdns_service)(C1) Found server 192.168.1.122 (port 3000) (connect)(C1) Signal K server has been found at address 192.168.1.122:3000 by mDNS. (connect)(C1) Websocket is connecting to Signal K server on address 192.168.1.122:3000 (test_token)(C1) Testing token with url http://192.168.1.122:3000/signalk/ (test_token)(C1) Testing resulted in http status 200 (test_token)(C1) Returned payload (length 255) is: (test_token)(C1) {"endpoints":{"v1":{"version":"1.41.0","signalk-http":"http://192.168.1.122:3000/signalk/v1/api/","signalk-ws":"ws://192.168.1.122:3000/signalk/v1/stream","signalk-tcp":"tcp://192.168.1.122:8375"}},"server":{"id":"signalk-server-node","version":"1.41.0"}} (test_token)(C1) End of payload output (test_token)(C1) Attempting to connect to Signal K Websocket... [E][WiFiClient.cpp:485] flush(): fail on fd 61, errno: 11, "No more processes"

SystemHz.zip

mairas commented 2 years ago

Based on your description, the sensor library you're using is probably performing the read operation in a blocking fashion. If each read operation takes, say, 500 ms, and you're blocking the program execution for that duration, then it's quite obvious that you're not going to be able to read more than a couple of sensors per second.

See if you can read the sensor or the library in a continuous or async mode. That would resolve your problem.

mairas commented 2 years ago

Also: SystemHz works exactly as intended in this case.

nadrog commented 2 years ago

Thanks Matti, I understand it now.

Could you please help me with how the code should look like? I understand I have two options:

  1. Use sync calls like bme680.readTemperature() and embedd them like described in example async_repeat_sensor.cpp
  2. Use async calls like bme680.beginReading() and then take values from bme680 using bme680.temperature after reading (which takes cca 200ms) is done

I was trying to follow example in async_repeat_sensor.cpp but failed. This is what I came up with:

auto read_temperature_callback = [](RepeatSensor<float>* sensor) {
    app.onDelay( 0, [sensor]() { sensor->emit(bme680.readTemperature() + 273.15); });
};
auto* bme680_temperature = new RepeatSensor<float>(read_interval, read_temperature_callback);
bme680_temperature->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature","","K"));

but I have the same issue.

Help appreciated. Thanks!

mairas commented 2 years ago

You can't use sync calls unless you're calling them in separate FreeRTOS Tasks, but that's a much more complex topic. All ReactESP reactions are still in the same thread, so if you're making a blocking call anywhere, it'll block the whole program execution. But you're already close with your code. Something like this should work:

auto read_temperature_callback = [](RepeatSensor<float>* sensor) {
    bme680.beginReading();
    app.onDelay(300, [sensor]() { sensor->emit(bme680.temperature + 273.15); });
};
auto* bme680_temperature = new RepeatSensor<float>(read_interval, read_temperature_callback);
bme680_temperature->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature","","K"));

What happens there is that RepeatSensor calls the callback function every read_interval seconds. The callback first triggers the reading and then fires off a delay reaction, calling sensor->emit(...) after a 300 ms delay. All individual calls should return quickly, so you should be still getting good performance.

nadrog commented 2 years ago

Thanks for the clarification! At the end, my code looks like this:

  auto read_temperature_callback = [](RepeatSensor<float>* sensor) {
    bme680.beginReading();
    app.onDelay(2 * bme680.remainingReadingMillis(), [sensor]() { 
      bme680.endReading();
      sensor->emit(bme680.temperature + 273.15); 
    });
  };  
  auto* bme680_temperature = new RepeatSensor<float>(read_interval, read_temperature_callback);
  bme680_temperature->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature","","K"));

and systemHz is cca 160k with 4 sensors.

Since all 4 values in the physical sensor are read at the same time, would you have any suggestion for improvement so that I could call begin/endReading just once and then somehow emit 4 separate values?

mairas commented 2 years ago

Since all 4 values in the physical sensor are read at the same time, would you have any suggestion for improvement so that I could call begin/endReading just once and then somehow emit 4 separate values?

Yes, that can be done. Instead of using RepeatSensor, use an onRepeat reaction to trigger the readings and write the results to ObservableValues. Here's an example with two values:


// either instantiate these outside of `setup()` or on the heap (with `new`)
ObservableValue<float> temp1;
ObservableValue<float> temp2;

app.onRepeat(read_interval, []() {
    bme680.beginReading();
    app.onDelay(2 * bme680.remainingReadingMillis(), [sensor]() { 
      bme680.endReading();
      temp1 = bme680.temperature + 273.15; 
      temp2 = bme680.temperature2 + 273.15; 
    });
  };  
  temp1->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature1","","K"));
  temp2->connect_to(new SKOutputFloat("sensorDevice.SailorHat.bme680.temperature2","","K"));

Not sure how you'd actually read multiple values with the BME680 library, but you get the point.

Since the original misunderstanding got cleared out, I'm closing this issue. If you have further questions on how to use SensESP, don't hesitate to ask on the GitHub repo discussions or on the Slack channel!