devices.esphome.io
Smart Oil Gauge
Smart Oil Gauge
Device Type: sensorElectrical Standard: usBoard: esp8266Difficulty: Disassembly required, 3/5
      
  
        
Product Page: https://www.smartoilgauge.com/shop/product/ccf901i/
SKU: CCF-901
Pinout
| Pin | Function | 
|---|---|
| GPIO14 | Ultrasonic Power | 
| GPIO12 | Control Button (HIGH = off, LOW = on) | 
| GPIO13 | System Timer DONE (TP5111) | 
| GPIO15 | Analog Switch SELECT (SN74LVC1G3157) | 
| GPIO2 | Control Board LED (HIGH = off, LOW = on) | 
| GPIO0 | UART download | 
| GPIO16 | Connected to RST | 
| A0 | Temperature or Battery Voltage | 
| RST | Reset, Connected to GPIO16 | 
| GPIO5 | Ultrasonic Echo (JSN-SR04T) | 
| TXD | UART0_TXD | 
| RXD | UART0_RXD | 
| GPIO4 | Ultrasonic Trigger (JSN-SR04T) | 
Flashing
1) REMOVE THE BATTERIES!!
2) Remove the control board
3) Locate the contact points required for Physically Connecting to your Device: \
      
  
        
        
        
        
Operation
Note: Be sure to modify the substitution section of the code for your tank_size and tank_orientation. \ It is highly recommended to use an external 6.5 to 7.4 VDC power supply. Running this code will likely deplete the batteries faster than the stock firmware.
The controller wakes every hour, sends three level readings to Home Assistant, and then powers down for another hour waiting for the TPL5111 to power it back up.
Pressing the control button once will either wake up the controller, or power it back down.
Double pressing the control button while powered on will toggle between allowing and not allowing the automatic power down. When the controller is on, and the automatic power down is allowed, the LED on the control board will be OFF and briefly blink ON every second. When the controller is on, and the automatic power down is not allowed, the LED on the control board will be ON and briefly blink OFF every second. Wait about 15 to 20 seconds after waking the controller before attempting to disable the automatic power down. Disabling the Automatic power down gives time to flash Esphome code updates.
| LED Behavior | Controller State (Double Press Control Button to switch states) | 
|---|---|
| Short Blink ON every Second | Controller is awake, after sending 3 oil volume measurements, will power down for 1 hour. (Default State) | 
| Short Blink OFF every Second | Controller is awake, will continue to stay awake until restarted. Useful for reprogramming. | 
Ultrasonic JSN-SR04T
Datasheet: https://components101.com/sites/default/files/component_datasheet/JSN-SR04-Datasheet.pdf \
      
  
        
IC References
      
  
        
TPL5111 - Nano-Power System Timer for Power Gating
SMD Marking: ZFVX \ https://www.ti.com/lit/ds/symlink/tpl5111.pdf?ts=1739630376626&ref_url=https%253A%252F%252Fwww.google.com%252F
Power Gating of 3.3VDC to the ESP8266.\ Hardwired to restart the controller every hour.\ Timer will cut 3.3V when DONE pin goes HIGH, will wake after 1hr.
SN74LVC1G3157 - Single-Pole Double-Throw Analog Switch
SMD Marking: C5F \ Datasheet: https://www.ti.com/lit/ds/symlink/sn74lvc1g3157.pdf?ts=1740393486499
Switches the connection to A0 (GPIO17) | SELECT (GPIO15) | A0 (GPIO17) | | --------------- | ---------------------------- | | LOW | Battery Voltage* | | HIGH | Temperature (MCP9700AT-E/TT) |
*Battery Voltage is measured with a voltage divider circuit using R1= 10MOhm, R2= 1MOhm.
MCP9700AT-E/TT - Low-Power Linear Active Thermistor IC
SMD Marking: AFT3 \ Datasheet: https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP970X-Family-Data-Sheet-DS20001942.pdf
Basic Configuration
substitutions:  device_name: "smart-oil-gauge"  friendly_name: "Smart Oil Gauge"  Samples_Before_Sleep: '3'  Auto_Sleep_On_dc: '2.5%'  Auto_Sleep_Off_dc: '87%'  ultrasonic_interval: '250ms'  tank_size: "330" # 275, 330, 500, 550, or 1000  tank_orientation: '1'  # 1 = Vertical,  2 = Horizontal
esphome:  name: ${device_name}  friendly_name: ${friendly_name}  on_boot:    - priority: 800      then:        - switch.turn_off: ultrasonic_en        - switch.turn_off: deep_sleep_trig        - switch.turn_off: VarCheck        - switch.turn_off: ultrasonic_pwr        - switch.turn_off: Auto_Sleep_Disable    - priority: -100      then:        - script.execute: set_tank_dimensions        - output.turn_on: LED_pwm        - output.set_level:            id: LED_pwm            level: ${Auto_Sleep_On_dc}        - switch.turn_on: TempSens_EN
  on_shutdown:    then:      - switch.turn_off: ultrasonic_en      - output.turn_off: LED_pwm      - if:          condition:            - switch.is_on: deep_sleep_trig          then:            - switch.turn_on: sleep_1hr
esp8266:  board: esp_wroom_02
# Enable logginglogger:
# Enable Home Assistant APIapi:  encryption:    key: !secret api_encryption_key
ota:  - platform: esphome    password: !secret ota_password
wifi:  ssid: !secret wifi_ssid  password: !secret wifi_password
  # Enable fallback hotspot (captive portal) in case wifi connection fails  ap:    ssid: ${device_name}    password: !secret fallback_password
captive_portal:
deep_sleep:  id: my_deep_sleep  sleep_duration: 30s
globals:  - id: Tank_Width    type: float    initial_value: '0'
  - id: Tank_Radius    type: float    initial_value: '0'
  - id: Tank_Height    type: float    initial_value: '0'
  - id: Tank_Length    type: float    initial_value: '0'
  - id: Oil_Height    type: double    initial_value: '0'
  - id: Rectangle_Height    type: double    initial_value: '0'
  - id: Arc_Height    type: double    initial_value: '0'
  - id: Rectangle_Area    type: double    initial_value: '0'
  - id: Arc_Area    type: double    initial_value: '0'
  - id: Total_Area    type: double    initial_value: '0'
  - id: Oil_In_Tank    type: double    initial_value: '0'
  - id: Max_Fill    type: double    initial_value: '0'
  - id: Tank_Orientation    type: float    initial_value: ${tank_orientation}
  - id: Tank_Size    type: float    initial_value: ${tank_size}
  - id: Measure_Count    type: int    initial_value: '0'
  - id: Samples_Before_Sleep    type: int    initial_value: ${Samples_Before_Sleep}
switch:  - platform: restart    name: Reboot
  - platform: template    name: "Ultrasonic EN"    id: ultrasonic_en    entity_category: "config"    disabled_by_default: true    optimistic: true    on_turn_on:      then:        - script.execute: ultrasonic_loop    on_turn_off:      then:        - switch.turn_off: ultrasonic_pwr
  - platform: template    name: "Deep Sleep Trigger"    id: deep_sleep_trig    optimistic: True    on_turn_on:      then:        - script.stop: ultrasonic_loop        - switch.turn_off: ultrasonic_en        - deep_sleep.enter:            id: my_deep_sleep
  - platform: template    name: "Auto Sleep Disable"    id: Auto_Sleep_Disable    optimistic: True    on_turn_on:      - output.set_level:          id: LED_pwm          level: ${Auto_Sleep_Off_dc}    on_turn_off:      - output.set_level:          id: LED_pwm          level: ${Auto_Sleep_On_dc}
  - platform: template    name: "Variable Check"    entity_category: "config"    id: VarCheck    optimistic: True
  - platform: gpio    pin: GPIO15    id: TempSens_EN    name: "TempSens EN"    entity_category: "config"    disabled_by_default: true
  - platform: gpio    pin: GPIO14    id: ultrasonic_pwr    name: "Ultrasonic Pwr"    entity_category: "config"    disabled_by_default: false    on_turn_on:      then:        - delay: 5s        - switch.turn_on: ultrasonic_en
  - platform: gpio    pin: GPIO13 # Done Signal to TPL5111    id: sleep_1hr # Use Deep_Sleep_EN    entity_category: "config"    disabled_by_default: true    name: "Sleep 1hr"
output:  - platform: slow_pwm    id: LED_pwm    period: 1s    pin: GPIO2    inverted: True
binary_sensor:  - platform: gpio    pin:      number: GPIO12      inverted: true    id: ctrl_btn    name: "Control Button"    on_multi_click:      - timing:          - ON for at most 1s          - OFF for at most 1s          - ON for at most 1s          - OFF for at least 0.2s        then:          - switch.toggle: Auto_Sleep_Disable      - timing:          - ON for at most 1s          - OFF for at least 0.5s        then:          - switch.turn_on: deep_sleep_trig
sensor:  - platform: template    name: 'Oil In Tank'    id: Oil_In_Tank_sens    device_class: volume_storage    state_class: measurement    unit_of_measurement: 'gal'    accuracy_decimals: 4
  - platform: template    name: 'Max Fill'    id: Max_Fill_sens    device_class: volume    state_class: total    unit_of_measurement: 'gal'    accuracy_decimals: 4
  - platform: adc    pin: A0    name: "ADC Input"    id: ADC_Input    accuracy_decimals: 5    update_interval: 5s    entity_category: "diagnostic"    filters:      - lambda: |-
          // Battery Voltage Divider R Values          int R1 = 10;  // MOhm          int R2 = 1;   // MOhm
          float offset = -2.2; // Temperature correction offset (degC)
          if (id(TempSens_EN).state){            return (((x*1000)-500)/10) + offset; // Temperature Sensor (degC)          } else {            return (x * (R1+R2))/R2; // Battery Voltage          }
    on_value:      then:        - if:            condition:              - switch.is_on: TempSens_EN            then:              - sensor.template.publish:                  id: TempC                  state: !lambda 'return id(ADC_Input).state;'              - component.update: VP_Oil              - switch.turn_off: TempSens_EN              - delay: 1s              - switch.turn_on: ultrasonic_pwr            else:              - sensor.template.publish:                  id: Batt_V                  state: !lambda 'return id(ADC_Input).state;'
  - platform: template    name: 'Temperature'    id: TempC    device_class: temperature    state_class: measurement    accuracy_decimals: 3    unit_of_measurement: '°C'
  - platform: template    name: 'Battery Voltage'    id: Batt_V    device_class: voltage    state_class: measurement    accuracy_decimals: 4    unit_of_measurement: 'V'
  - platform: template    name: 'Vapor Pressure Oil'    id: VP_Oil    update_interval: never    device_class: pressure    unit_of_measurement: kPa    entity_category: "diagnostic"    accuracy_decimals: 8    lambda: |-      return id(TempC).state;    filters:      - calibrate_linear:          method: exact          datapoints:            # https://www.eng-tips.com/threads/typical-diesel-info.109718/post-424028            # Map 0.0 (from sensor) to 1.0 (true value)            # degC to Oil Vapor Pressure kPa            - 4.444444444 -> 0.021373756            - 10.0 -> 0.03102642            - 15.55555556 -> 0.051021224            - 21.11111111 -> 0.06205284            - 26.66666667 -> 0.08273712            - 32.22222222 -> 0.11031616            - 37.77777778 -> 0.15168472
  - platform: ultrasonic    trigger_pin:        number: GPIO4        inverted: true
    echo_pin: GPIO5    name: "Distance to Oil"    id: Oil_Distance    accuracy_decimals: 25    update_interval: never # 4s    filters:
      - median:          window_size: 5          send_every: 5          send_first_at: 5
      - sliding_window_moving_average:          window_size: 16          send_every: 16          send_first_at: 16
      - lambda: |-
          // Calc Molecular Weight of Vapor above Oil
          double MW_Oil = 167.31102; // g_Oil/mol_Oil  (C12H23)          double MW_Air = 28.9639475; // g_Air/mol_Air
          double P_Total = 101.325; // kPa (Could use a measured pressure here)          double P_Oil = id(VP_Oil).state; // Partial Pressure of Oil kPa          double P_Air = P_Total - P_Oil; // Partial Pressure Of Air kPa
          double nOil_per_nTotal = P_Oil / P_Total; // mol_Oil/mol_Total          double nAir_per_nTotal = P_Air / P_Total; // mol_Air/mol_Total
          double g_Oil_per_nTotal = nOil_per_nTotal * MW_Oil; // g_Oil/mol_Total          double g_Air_per_nTotal = nAir_per_nTotal * MW_Air; // g_Air/mol_Total
          double MW_Total = g_Oil_per_nTotal + g_Air_per_nTotal; // g_Total/mol_Total          MW_Total = MW_Total / 1000; // kg_Totla/mol_Total
          // Back Calc Time of Flight          double ESP_speed_sound_m_per_s = 343.0;          double total_dist = x * 2.0;          double time_s = total_dist / ESP_speed_sound_m_per_s;
          // Calc Speed of Sound in Vapor above Oil          // https://en.wikipedia.org/wiki/Speed_of_sound          double gamma = 1.4; // Oil is small enough fraction that large change in gamma is not expected          double R = 8.31446261815324;          double Sc = sqrt(gamma * R * 273.15 / MW_Total);          double speed_sound_m_per_s = Sc * sqrt(1+(id(TempC).state/273.15)); // ideal diatomic gas
          // Calc Distance to Oil Surface          total_dist = time_s * speed_sound_m_per_s;          return total_dist/2.0;
    on_value:      then:        - script.execute: Calc_Oil_Height
script:
  - id: Calc_Oil_Height    then:      - if:          condition:            - lambda: |-                return id(Tank_Orientation) == 1; // Vertical          then:            - lambda: |-                id(Oil_Height) = id(Tank_Height) - (id(Oil_Distance).state * 1000 / 25.4);            - script.execute: Check_Oil_Height_V      - if:          condition:            - lambda: |-                return id(Tank_Orientation) == 2; // Horizontal          then:            - lambda: |-                id(Oil_Height) = id(Tank_Width) - (id(Oil_Distance).state * 1000 / 25.4);            - script.execute: Check_Oil_Height_H
  - id: Check_Oil_Height_H    then:      - lambda: |-          id(Rectangle_Height) = id(Oil_Height);          id(Arc_Height) = id(Oil_Height);      - script.execute: Calc_Area
  - id: Check_Oil_Height_V    then:      - if:          condition:            lambda: |-              return id(Oil_Height) >= (id(Tank_Height)-id(Tank_Radius));          then:            - lambda: |-                id(Rectangle_Height) = id(Tank_Height) - id(Tank_Width);                id(Arc_Height) = id(Oil_Height) - id(Rectangle_Height);            - script.execute: Calc_Area      - if:          condition:            all:              - lambda: |-                  return id(Oil_Height) >= (id(Tank_Radius));              - lambda: |-                  return id(Oil_Height) < (id(Tank_Height)-id(Tank_Radius));          then:            - lambda: |-                id(Rectangle_Height) = id(Oil_Height) - id(Tank_Radius);                id(Arc_Height) = id(Tank_Radius);            - script.execute: Calc_Area      - if:          condition:            all:              - lambda: |-                  return id(Oil_Height) >= 0;              - lambda: |-                  return id(Oil_Height) < (id(Tank_Radius));          then:             - lambda: |-                id(Rectangle_Height) = 0;                id(Arc_Height) = id(Oil_Height);             - script.execute: Calc_Area
  - id: Calc_Area    then:      - lambda: |-          double d;          double r;          double arc;
          d = id(Arc_Height);          r = id(Tank_Radius);          arc = 2 * (acos((r-d)/r));          id(Arc_Area) = ((r*r) * (arc - sin(arc))) / 2;      - lambda: |-          if (id(Tank_Orientation) == 1){ // Vertical            id(Rectangle_Area) = id(Rectangle_Height) * id(Tank_Width);          } else { // Horizontal            id(Rectangle_Area) = id(Rectangle_Height) * (id(Tank_Height) - id(Tank_Width));          }
      - lambda: |-          id(Total_Area) = id(Rectangle_Area) + id(Arc_Area);      - script.execute: Calc_Oil_Volume
  - id: Calc_Oil_Volume    then:      - globals.set:          id: Oil_In_Tank          value: !lambda |-            return id(Total_Area) * id(Tank_Length) / 231;      - sensor.template.publish:          id: Oil_In_Tank_sens          state: !lambda 'return id(Oil_In_Tank);'      - sensor.template.publish:          id: Max_Fill_sens          state: !lambda 'return id(Max_Fill) - id(Oil_In_Tank);'      - if:          condition:            - switch.is_on: Auto_Sleep_Disable          then:            - globals.set:                id: Measure_Count                value: '0'          else:            - globals.set:                id: Measure_Count                value: !lambda 'return id(Measure_Count) += 1;'            - if:                condition:                  - lambda: 'return id(Measure_Count) >= id(Samples_Before_Sleep);'                then:                  - switch.turn_on: deep_sleep_trig      - lambda: 'ESP_LOGD("MeasureCount", "%i", id(Measure_Count));'
      - if:          condition:            - switch.is_on: VarCheck          then:            - script.execute: Log_Values
  - id: Log_Values    mode: queued    then:      - lambda: |-          ESP_LOGD("VarCheck", "Tank_Orientation %.15g", id(Tank_Orientation));          ESP_LOGD("VarCheck", "Tank_Size %.15g", id(Tank_Size));          ESP_LOGD("VarCheck", "Oil_Distance %.15g", id(Oil_Distance).state);          ESP_LOGD("VarCheck", "Tank_Width %.15g", id(Tank_Width));          ESP_LOGD("VarCheck", "Tank_Height %.15g", id(Tank_Height));          ESP_LOGD("VarCheck", "Tank_Length %.15g", id(Tank_Length));          ESP_LOGD("VarCheck", "Tank_Radius %.15g", id(Tank_Radius));          ESP_LOGD("VarCheck", "Oil_Height %.15g", id(Oil_Height));          ESP_LOGD("VarCheck", "Arc_Height %.15g", id(Arc_Height));          ESP_LOGD("VarCheck", "Rectangle_Height %.15g", id(Rectangle_Height));          ESP_LOGD("VarCheck", "Arc_Area %.15g", id(Arc_Area));          ESP_LOGD("VarCheck", "Rectangle_Area %.15g", id(Rectangle_Area));          ESP_LOGD("VarCheck", "Total_Area%.15g", id(Total_Area));          ESP_LOGD("VarCheck", "Oil_In_Tank%.15g", id(Oil_In_Tank));
  - id: ultrasonic_loop    mode: restart    then:      - delay: ${ultrasonic_interval}      - while:          condition:            switch.is_on: ultrasonic_en          then:            - component.update: Oil_Distance            - delay: ${ultrasonic_interval}
 # Tank Dimensions: # https://www.fuelsnap.com/heating_oil_tank_charts.php
  - id: set_tank_dimensions    then:      - lambda: |-          if (id(Tank_Size) == 275){              id(Max_Fill) = 250;              id(Tank_Width) = 27.8;              id(Tank_Height) = 44;              id(Tank_Length) = 60;          }          if (id(Tank_Size) == 330){              id(Max_Fill) = 300;              id(Tank_Width) = 27.8;              id(Tank_Height) = 44;              id(Tank_Length) = 72;          }          if (id(Tank_Size) == 500){              id(Max_Fill) = 450;              id(Tank_Width) = 48;              id(Tank_Height) = 48;              id(Tank_Length) = 63.8;          }          if (id(Tank_Size) == 550){              id(Max_Fill) = 500;              id(Tank_Width) = 48;              id(Tank_Height) = 48;              id(Tank_Length) = 70.25;          }          if (id(Tank_Size) == 1000){              id(Max_Fill) = 900;              id(Tank_Width) = 48;              id(Tank_Height) = 48;              id(Tank_Length) = 127.6;          }
          id(Tank_Radius) = id(Tank_Width)/2;