sdc41 with bmp280 issue – wrong ppm value – zero ppm

  • Today got to know why my sdc41 sensor was showing bad data earlier it used to show 500 in day and 600-800ppm in night when door was closed. #sdc41 #esp32
    • It was paired with bmp280 and it was using pressure to Compensate the co2. but bmp 280 was showing bad value randomly about -149 hpa
    • As it was using BMP280 value it was started showing double and after few days it started showing 0 ppm
    • Now i ran simple code to just get value of sdc41 it was showing 23000 which more than current limit of sdc41 which is 5000
    • Ran frc 420 to self calibrate the sensor with current value to 420 reference
  • Ran auto calibration , everything looks good now.
#include <Arduino.h>
#include <Wire.h>
#include <SparkFun_SCD4x_Arduino_Library.h>

// NodeMCU/ESP8266 I2C pins: D2 = SDA, D1 = SCL
static const uint8_t SDA_PIN = D2;
static const uint8_t SCL_PIN = D1;

static const uint16_t MAX_VALID_CO2_PPM = 5000;
static const uint16_t DEFAULT_FRC_REFERENCE_PPM = 420; // Typical fresh-air reference
static const uint32_t FRC_STABILIZE_MS = 180000;       // >= 3 minutes per datasheet
static const float DEFAULT_AMBIENT_PRESSURE_HPA = 1006.0f;

SCD4x scd4x(SCD4x_SENSOR_SCD41);

bool applyAmbientPressureCompensation(float pressureHpa, bool persist = true) {
  if (pressureHpa < 700.0f || pressureHpa > 1200.0f) {
    Serial.println("ERROR: pressure out of expected range (700..1200 hPa).");
    return false;
  }

  const float pressurePa = pressureHpa * 100.0f;

  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.setAmbientPressure(pressurePa)) {
    Serial.println("ERROR: Failed to set ambient pressure compensation.");
    scd4x.startPeriodicMeasurement();
    return false;
  }

  if (persist && !scd4x.persistSettings()) {
    Serial.println("WARNING: Ambient pressure set but persistSettings failed.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Failed to restart periodic measurement after pressure update.");
    return false;
  }

  Serial.print("Ambient pressure compensation set to ");
  Serial.print(pressureHpa, 1);
  Serial.println(" hPa");
  return true;
}

bool configureAutoSelfCalibration(bool enabled) {
  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.setAutomaticSelfCalibrationEnabled(enabled)) {
    Serial.println("ERROR: Failed to set ASC state.");
    return false;
  }

  if (!scd4x.persistSettings()) {
    Serial.println("WARNING: ASC updated but persistSettings failed.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Failed to restart periodic measurement after ASC config.");
    return false;
  }

  Serial.print("ASC is now ");
  Serial.println(enabled ? "ENABLED" : "DISABLED");
  return true;
}

bool runForcedRecalibration(uint16_t referencePpm) {
  if (referencePpm < 350 || referencePpm > 2000) {
    Serial.println("ERROR: Reference ppm out of expected FRC range (350..2000).");
    return false;
  }

  Serial.println();
  Serial.println("Starting Forced Recalibration (FRC)...");
  Serial.println("Keep sensor in stable, known CO2 environment.");
  Serial.print("Reference CO2 (ppm): ");
  Serial.println(referencePpm);

  scd4x.stopPeriodicMeasurement();
  delay(500);
  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not start periodic measurement for FRC preconditioning.");
    return false;
  }

  // Datasheet requirement: run in normal mode for at least 3 minutes before FRC.
  const uint32_t startMs = millis();
  while (millis() - startMs < FRC_STABILIZE_MS) {
    const uint32_t remaining = (FRC_STABILIZE_MS - (millis() - startMs)) / 1000;
    Serial.print("FRC preconditioning... ");
    Serial.print(remaining);
    Serial.println(" s remaining");

    if (scd4x.readMeasurement()) {
      Serial.print("  sample CO2(ppm): ");
      Serial.println(scd4x.getCO2());
    }

    delay(5000);
  }

  scd4x.stopPeriodicMeasurement();
  delay(500);

  float correction = 0.0f;
  if (!scd4x.performForcedRecalibration(referencePpm, &correction)) {
    Serial.println("ERROR: FRC failed.");
    scd4x.startPeriodicMeasurement();
    return false;
  }

  Serial.print("FRC correction applied (ppm): ");
  Serial.println(correction, 1);

  if (!scd4x.persistSettings()) {
    Serial.println("WARNING: FRC succeeded but persistSettings failed.");
  }

  if (!scd4x.reInit()) {
    Serial.println("WARNING: reInit failed after FRC.");
  }

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not restart periodic measurement after FRC.");
    return false;
  }

  Serial.println("FRC complete. Sensor restarted in periodic mode.");
  return true;
}

void handleSerialCommands() {
  if (!Serial.available()) {
    return;
  }

  String cmd = Serial.readStringUntil('\n');
  cmd.trim();
  cmd.toLowerCase();

  if (cmd == "help") {
    Serial.println("Commands:");
    Serial.println("  help         -> show commands");
    Serial.println("  asc on       -> enable automatic self-calibration");
    Serial.println("  asc off      -> disable automatic self-calibration");
    Serial.println("  frc          -> run forced recalibration with 420 ppm reference");
    Serial.println("  frc <ppm>    -> run forced recalibration with custom reference");
    Serial.println("  pressure     -> set pressure compensation to 1006 hPa");
    return;
  }

  if (cmd == "asc on") {
    configureAutoSelfCalibration(true);
    return;
  }

  if (cmd == "asc off") {
    configureAutoSelfCalibration(false);
    return;
  }

  if (cmd == "frc") {
    runForcedRecalibration(DEFAULT_FRC_REFERENCE_PPM);
    return;
  }

  if (cmd.startsWith("frc ")) {
    int ppm = cmd.substring(4).toInt();
    if (ppm <= 0) {
      Serial.println("ERROR: Invalid ppm. Example: frc 420");
      return;
    }
    runForcedRecalibration((uint16_t)ppm);
    return;
  }

  if (cmd == "pressure") {
    applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);
    return;
  }

  Serial.print("Unknown command: ");
  Serial.println(cmd);
  Serial.println("Type 'help' for available commands.");
}

void setup() {
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println("SCD41 minimal CO2 test starting...");

  Wire.begin(SDA_PIN, SCL_PIN);
  Wire.setClock(100000);

  if (!scd4x.begin(true, true, false)) {
    Serial.println("ERROR: SCD41 init failed. Check wiring, power, and I2C address.");
    while (true) {
      delay(1000);
    }
  }

  // Ensure periodic mode starts from a clean state.
  scd4x.stopPeriodicMeasurement();
  delay(500);

  if (!scd4x.startPeriodicMeasurement()) {
    Serial.println("ERROR: Could not start low-power periodic measurement.");
    while (true) {
      delay(1000);
    }
  }

  // Recommended for SCD41 long-term stability in real deployments.
  configureAutoSelfCalibration(true);

  // Overwrite any previously saved bad pressure setting (e.g. from a faulty upstream sensor)
  // and persist the correct local pressure compensation.
  applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);

  Serial.println("SCD41 initialized. First reading can take ~30s.");
  Serial.println("Type 'help' in Serial Monitor for calibration commands.");
}

void loop() {
  handleSerialCommands();

  if (!scd4x.readMeasurement()) {
    Serial.println("Measurement not ready yet...");
    delay(5000);
    return;
  }

  const uint16_t co2 = scd4x.getCO2();
  const float temperature = scd4x.getTemperature();
  const float humidity = scd4x.getHumidity();

  Serial.print("CO2(ppm): ");
  Serial.print(co2);
  Serial.print("  Temp(C): ");
  Serial.print(temperature, 2);
  Serial.print("  RH(%): ");
  Serial.println(humidity, 2);

  if (co2 == 0) {
    Serial.println("WARNING: CO2 is 0 ppm (invalid on SCD41). Sensor may still be stabilizing or faulty.");
  } else if (co2 > MAX_VALID_CO2_PPM) {
    Serial.println("WARNING: CO2 is above SCD41 range (5000 ppm). Reading likely invalid; run 'frc 420' in fresh air.");
  }

  delay(5000);
}
FRC preconditioning... 24 s remaining
  sample CO2(ppm): 23076
FRC preconditioning... 19 s remaining
  sample CO2(ppm): 23076
FRC preconditioning... 14 s remaining
  sample CO2(ppm): 23077
FRC preconditioning... 9 s remaining
  sample CO2(ppm): 23075
FRC preconditioning... 4 s remaining
  sample CO2(ppm): 23078
FRC correction applied (ppm): -22518.0
FRC complete. Sensor restarted in periodic mode.
Measurement not ready yet...
CO2(ppm): 471  Temp(C): 30.83  RH(%): 73.06
CO2(ppm): 472  Temp(C): 30.39  RH(%): 74.56
CO2(ppm): 488  Temp(C): 30.14  RH(%): 75.75
CO2(ppm): 486  Temp(C): 29.92  RH(%): 76.85
CO2(ppm): 469  Temp(C): 29.68  RH(%): 77.88

raspberry pi useful commands

  • See voltage of all pin – can be used in TRNG
root@lp-arm-5:~# vcgencmd pmic_read_adc
 3V7_WL_SW_A current(0)=0.09271335A
   3V3_SYS_A current(1)=0.07026696A
   1V8_SYS_A current(2)=0.12589500A
  DDR_VDD2_A current(3)=0.00292779A
  DDR_VDDQ_A current(4)=0.00000000A
   1V1_SYS_A current(5)=0.20299340A
    0V8_SW_A current(6)=0.35523850A
  VDD_CORE_A current(7)=0.68761000A
   3V3_DAC_A current(17)=0.00000000A
   3V3_ADC_A current(18)=0.00006105A
   0V8_AON_A current(16)=0.00323565A
      HDMI_A current(22)=0.01172160A
 3V7_WL_SW_V volt(8)=3.69977600V
   3V3_SYS_V volt(9)=3.30632200V
   1V8_SYS_V volt(10)=1.79975400V
  DDR_VDD2_V volt(11)=1.10732500V
  DDR_VDDQ_V volt(12)=0.60402870V
   1V1_SYS_V volt(13)=1.10256300V
    0V8_SW_V volt(14)=0.79926660V
  VDD_CORE_V volt(15)=0.72097620V
   3V3_DAC_V volt(20)=3.30768900V
   3V3_ADC_V volt(21)=3.30585700V
   0V8_AON_V volt(19)=0.79736190V
      HDMI_V volt(23)=5.02902000V
     EXT5V_V volt(24)=5.02098000V
      BATT_V volt(25)=0.00000000V

Openwrt openvpn client with local network

  • Zone – Add

Example (/etc/config/firewall):

config zone
    option name 'vpn'
    list network 'vpn'
    option input 'ACCEPT'
    option output 'ACCEPT'
    option forward 'ACCEPT'
    option masq '1'
    option mtu_fix '1'

config forwarding
    option src 'lan'
    option dest 'vpn'

And define the network:

config interface 'vpn'
    option proto 'none'
    option device 'tun0'

Then restart:

/etc/init.d/network restart
/etc/init.d/firewall restart

Run this on OpenWrt:

tcpdump -ni tun0 icmp

Then, from your laptop:

ping 192.168.0.1

  • Static route
  • Install openvpn
opkg install openvpn-openssl luci-app-openvpn
opkg install openvpn-easy-rsa
/etc/openvpn/client.ovpn

/etc/init.d/openvpn enable
/etc/init.d/openvpn start

/etc/init.d/openvpn status

logread -f openvpn

Openwrt and local router static route

  • static route on wifi router
  • static route on openwrt
  • network digram
  • Now i can do ssh from pi5 to ubuntu-laptop on 192.168.1.145 even when i disconnected form wifi on ubuntu-laptop.
root@lp-arm-5:~# ssh root@192.168.1.145 
root@192.168.1.145's password: 
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.17.0-14-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

376 updates can be applied immediately.
364 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

89 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm

Last login: Sat Jul 25 18:59:08 2026 from 192.168.10.118

Getting GPS location using NEO-7M-0-000 Blox with ESP32

#include <WiFi.h>

const char *ssid = "wifi";
const char *password = "pass";

// GPS UART
HardwareSerial GPS(2); // UART2

// Standard NMEA TCP port
WiFiServer server(10110);

// Allow multiple clients
WiFiClient clients[5];

String line;

void setup() {
  Serial.begin(115200);

  // GPS on GPIO21 (RX), GPIO22 (TX)
  GPS.begin(9600, SERIAL_8N1, 21, 22);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  Serial.print("Connecting");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.print("Connected: ");
  Serial.println(WiFi.localIP());

  server.begin();
  server.setNoDelay(true);

  Serial.println("NMEA TCP server started on port 10110");
}

void loop() {

  // Accept new clients
  if (server.hasClient()) {

    for (int i = 0; i < 5; i++) {

      if (!clients[i] || !clients[i].connected()) {

        if (clients[i])
          clients[i].stop();

        clients[i] = server.available();
        clients[i].setNoDelay(true);

        Serial.printf("Client %d connected\n", i);
        break;
      }
    }

    // No room
    WiFiClient reject = server.available();
    reject.stop();
  }

  // Read GPS
  while (GPS.available()) {

    char c = GPS.read();

    // Echo to USB serial
    Serial.write(c);

    line += c;

    if (c == '\n') {

      // Send complete NMEA sentence to all clients
      for (int i = 0; i < 5; i++) {

        if (clients[i] && clients[i].connected()) {

          clients[i].print(line);

          if (clients[i].getWriteError()) {
            clients[i].stop();
          }
        }
      }

      line.clear();

      // Prevent runaway if malformed data
      if (line.length() > 256)
        line.clear();
    }
  }
}
  • xgps to view data
apt install gpsd gpsd-clients
nc ESP-IP 10110
#OR
gpsd tcp://192.168.1.150:10110

#lauch
xgps

#or
sudo systemctl stop gpsd.socket gpsd
gpsd -N -n -D 5 tcp://192.168.10.196:10110
gpspipe -r
#lauch
xgps

#check time of laptop
home@home:~$ ntpdate -q 192.168.10.196
2026-07-28 17:58:10.0 (+0530) -1.950956 +/- 0.001631 192.168.10.196 s1 no-leap

Your computer's clock is about -1.950956 seconds ahead of the GPS-based NTP server. To match the GPS time, your clock would need to move backward by about -1.950956 seconds.

systemctl enable --now chrony

@home: timedatectl 
               Local time: Tue 2026-07-28 18:09:42 IST
           Universal time: Tue 2026-07-28 12:39:42 UTC
                 RTC time: Tue 2026-07-28 12:39:42
                Time zone: Asia/Kolkata (IST, +0530)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

@home:~$ chronyc tracking
Reference ID    : 41007738 (ec2-65-0-119-56.ap-south-1.compute.amazonaws.com)
Stratum         : 5
Ref time (UTC)  : Tue Jul 28 12:41:44 2026
System time     : 0.000236698 seconds fast of NTP time
Last offset     : +0.000414969 seconds
RMS offset      : 0.000378138 seconds
Frequency       : 14.685 ppm fast
Residual freq   : +1.339 ppm
Skew            : 21.187 ppm
Root delay      : 0.010673190 seconds
Root dispersion : 0.000975857 seconds
Update interval : 64.2 seconds
Leap status     : Normal


chronyc sources -v
  • GPS time after connecting PPS pin to D7
@home:~$ chronyc sources -v

MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
^* 192.168.10.196                1   6   377    40   -489ms[ -610ms] +/- 2671us
^- alphyn.canonical.com          2  10   367   366   -462ms[  +25ms] +/-  170ms
^- prod-ntp-4.ntp1.ps5.cano>     2  10   377    23   -486ms[ -486ms] +/-   76ms
^- prod-ntp-3.ntp4.ps5.cano>     2  10   377    54   -458ms[ -579ms] +/-   99ms
^- prod-ntp-5.ntp1.ps5.cano>     2  10   377   497   -486ms[+3998us] +/-   73ms
^- 172-233-155-39.ip.linode>     5   9   377    47   -484ms[ -605ms] +/-  154ms
^- 149.206.212.35.bc.google>     2  10   377   434   -472ms[  +18ms] +/-  167ms
^- time.cloudflare.com           3   6   377    44   -484ms[ -605ms] +/-   72ms
^- ntp2.ggsrv.de                 2   8   277   141   -442ms[ -654ms] +/-  123ms

#offset fix of 0.1 sec
home@home:~$ while true; do     ntpdate -q 192.168.10.196;     sleep 1; done
2026-07-29 12:49:02.649000 (+0530) -0.000654 +/- 0.002628 192.168.10.196 s1 no-leap
2026-07-29 12:49:03.711000 (+0530) -0.000765 +/- 0.002465 192.168.10.196 s1 no-leap
2026-07-29 12:49:04.767000 (+0530) -0.000835 +/- 0.002581 192.168.10.196 s1 no-leap
2026-07-29 12:49:05.830000 (+0530) -0.002471 +/- 0.003938 192.168.10.196 s1 no-leap
2026-07-29 12:49:06.895000 (+0530) -0.001071 +/- 0.002849 192.168.10.196 s1 no-leap
2026-07-29 12:49:07.969999 (+0530) -0.001286 +/- 0.002694 192.168.10.196 s1 no-leap
2026-07-29 12:49:09.57000 (+0530) +0.002670 +/- 0.006418 192.168.10.196 s1 no-leap
2026-07-29 12:49:10.113999 (+0530) -0.001435 +/- 0.001903 192.168.10.196 s1 no-leap
2026-07-29 12:49:11.193999 (+0530) -0.000183 +/- 0.003198 192.168.10.196 s1 no-leap
2026-07-29 12:49:12.251999 (+0530) -0.000521 +/- 0.002711 192.168.10.196 s1 no-leap
2026-07-29 12:49:13.350000 (+0530) -0.001052 +/- 0.002741 192.168.10.196 s1 no-leap
2026-07-29 12:49:14.455999 (+0530) -0.000497 +/- 0.002895 192.168.10.196 s1 no-leap
2026-07-29 12:49:15.520000 (+0530) -0.000422 +/- 0.002912 192.168.10.196 s1 no-leap
2026-07-29 12:49:16.622000 (+0530) +0.000399 +/- 0.003694 192.168.10.196 s1 no-leap
2026-07-29 12:49:17.690000 (+0530) +0.001445 +/- 0.004720 192.168.10.196 s1 no-leap
2026-07-29 12:49:18.751000 (+0530) -0.001759 +/- 0.001505 192.168.10.196 s1 no-leap
2026-07-29 12:49:19.813000 (+0530) -0.001110 +/- 0.003159 192.168.10.196 s1 no-leap
2026-07-29 12:49:20.876999 (+0530) -0.000605 +/- 0.002787 192.168.10.196 s1 no-leap


#Before correct PPS

^- 192.168.10.196                1   6   377    52    -15ms[  -15ms] +/-   17ms

^- 192.168.10.196                1   6   377    57    -67ms[  -67ms] +/-   68ms

^x 192.168.10.196                1   6   377    16   +707ms[ +707ms] +/- 3018us

^x 192.168.10.196                1   6   377     8  +1088ms[+1088ms] +/- 2698us


@home:~$ while true; do     ntpdate -q 192.168.10.196;     sleep 1; done
ntpdig: no eligible servers
2026-07-29 12:44:33.0 (+0530) -0.631612 +/- 0.002777 192.168.10.196 s1 no-leap
2026-07-29 12:44:34.0 (+0530) -0.708538 +/- 0.002867 192.168.10.196 s1 no-leap
2026-07-29 12:44:35.0 (+0530) -0.814500 +/- 0.045810 192.168.10.196 s1 no-leap
2026-07-29 12:44:36.0 (+0530) -0.925514 +/- 0.003359 192.168.10.196 s1 no-leap
2026-07-29 12:44:37.0 (+0530) -1.001159 +/- 0.003638 192.168.10.196 s1 no-leap
2026-07-29 12:44:38.0 (+0530) -1.063557 +/- 0.004615 192.168.10.196 s1 no-leap
2026-07-29 12:44:39.0 (+0530) -1.127265 +/- 0.003226 192.168.10.196 s1 no-leap
2026-07-29 12:44:41.0 (+0530) -0.272564 +/- 0.048974 192.168.10.196 s1 no-leap
2026-07-29 12:44:42.0 (+0530) -0.414035 +/- 0.002208 192.168.10.196 s1 no-leap
2026-07-29 12:44:43.0 (+0530) -0.521767 +/- 0.051125 192.168.10.196 s1 no-leap
  • What is PPM? Parts Per Million
|         Accuracy |      Drift per day |
| ---------------: | -----------------: |
|           50 ppm |       ~4.3 seconds |
|           20 ppm |       ~1.7 seconds |
|           10 ppm |      ~0.86 seconds |
|            1 ppm |   ~86 milliseconds |
|          0.5 ppm |   ~43 milliseconds |
|         0.01 ppm | ~0.86 milliseconds |
| 10⁻¹¹ (Rubidium) | ~0.86 microseconds |

So Raspberry pi has 54MHZ oscillator that can drif by 20-30ppm means 1-3 seconds per day.

ESP 8266 node auto reboot – ESPHome

  • logs
[10:24:37.762][E][api:128]: No clients; rebooting
[10:24:37.806][I][app:264]: Forcing a reboot
[10:24:38.079][W][wifi_esp8266:512]: Disconnected ssid='JioFiber-b44M3' bssid=34:D8:56:21:62:FA reason='Association Leave'\xff\xeaU\xfa[I][logger:032]: Log initialized
[10:24:38.151][E][esp8266:171]: *** CRASH DETECTED ON PREVIOUS BOOT ***
[10:24:38.231][E][esp8266:186]:   Reason: Hardware WDT - Level1Int (exccause=4)
[10:24:38.279][E][esp8266:191]:   PC: 0x40106487
WARNING Decoded 0x40106487: system_get_time
[10:24:38.343][C][safe_mode:189]: Unsuccessful boot attempts: 0
[10:24:38.394][I][app:060]: Running through setup()

This means the ESPHome API decided to reboot because no API client (typically Home Assistant or the ESPHome dashboard) was connected for the configured timeout.

By default, if you’re using the api: component, ESPHome can reboot after a period without an API connection.

  • Remove esphome api or set to 0
api:
  reboot_timeout: 0s

User Namespaces in pods – k8 1.36+

  • Without User Namespaces (old behavior)
Container root (UID 0)= Host root (UID 0)
  • With User Namespaces
Container root (UID 0)
        ↓ mapped to
Host UID 100000+ (unprivileged)

nginx-pod-userspace.yml

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  hostUsers: true
  containers:
  - image: nginx
    name: nginx

Screen shut off / light off / lid down

Keep Proxmox running normally
Turn off only the laptop screen/backlight
Prevent suspend/sleep on lid close

nano /etc/systemd/logind.conf

#add

HandleLidSwitch=ignore

HandleLidSwitchDocked=ignore
HandleSuspendKey=ignore
HandleHibernateKey=ignore

systemctl restart systemd-logind
  • Turn off the laptop display only
apt update
apt install vbetool
vbetool dpms off
vbetool dpms on