Měření radiace a publikování dat na safecast.org

Ucelené projekty, návody a postupy.
Odpovědět
kiklhorn
Moderátor
Moderátor
Příspěvky: 901
Registrován: 03. červenec 2021, 18:35
Dal poděkování: 107 poděkování
Dostal poděkování: 210 poděkování

Měření radiace a publikování dat na safecast.org

Příspěvek od kiklhorn »

Seznam dílů:
LilyGO TTGO-T-Display
https://www.tindie.com/products/iotdev/ ... r-counter/
volitelně Lipol o rozměru 803160 nebo menší.
Krabička
Odkaz na základní kód ze kterého vycházím na iot-devices.com.ua

Návod:
1) Registrace na api.safecast.org (zkopírovat si api key)
2) Modul geigeru - připojit napájení na nějaký 3V a GND na TTGO modulu. Připojit OUT geigeru na GPIO27 TTGO modulu.
3) Připojit Li-pol do konektoru TTGO modulu
4) Vložit do krabičky - ttgo modul dříve než zarážku displeje

DSC_1095.JPG
DSC_1099.JPG
ezgif.com-gif-maker (3).gif

ESPHome - geiger.yaml (pracovní verze)

Kód: Vybrat vše

# font_ikony: 
# Preview https://pictogrammers.github.io/@mdi/font/7.0.96/
# Download: https://cdnjs.com/libraries/MaterialDesign-Webfont/7.0.96
# Board: https://github.com/Xinyuan-LilyGO/TTGO-T-Display
# Sensor: https://www.tindie.com/products/iotdev/ggreg20_v3-ionizing-radiation-geiger-counter/ + SBM-20 Geiger tube
# Wiring: GPIO27 to Geiger module output, 3V & GND from TTGO-T-Display to Geiger. Don't power Geiger directly from Li-on, no enough charging current from TTGO-T-Display to recharge li-pol.
# Box: https://www.printables.com/model/330675
# Li-pol (optional) - max. size 803160 to fit in the box

substitutions:
  device_name: geiger

esphome:
  name: $device_name

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  # deassert_rts_dtr: true
  # baud_rate: 0
  level: error

# Enable Home Assistant API
api:
  encryption:
    key: "qi52lSM8jUZ748mLKUJyjKBGkg2cdEUjsfUh6o4KRDQ="

ota:
  password: "67cf31efcde5fb212ca4d45adc43dbd6"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Geiger Fallback Hotspot"
    password: "kk9pPltibq5Q"

captive_portal:

### CUSTOM!!

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO19

time:
  - platform: homeassistant
    id: esptime

switch:
  - platform: gpio
    pin: GPIO4
    name: "${device_name} Backlight"
    id: backlight   

binary_sensor:

  - platform: status
    name: "${device_name} Node Status"
    id: system_status       

  - platform: template
    device_class: safety
    name: "${device_name} Radiation Warning"
    # # This doesn't necessarily represent a "dangerous" count, but one that is abnormally high
    lambda: |-
      if (id(cpm).state > 100) {
        // High Count.
        return true;
      } else {
        // Normal Count.
        return false;
      }
        

  - platform: template
    name: "${device_name} pulse detect"
    internal: true
    # update_interval: 0ms
    id: pulse_detect
    on_press:
      then:
        - lambda:
            ESP_LOGE("pulse_detect","pulse detected");
        - component.update: mydisplay
    on_release: 
      then:
        # - delay: 50ms 
        - delay: 5ms 
        - lambda:
            ESP_LOGE("pulse_detect","pulse cleared");
        - component.update: mydisplay
  #   lambda: |-
  #     if (id(pulse_input).state) {

  #     }
text_sensor:
  - platform: template
    name: Uptime Human Readable
    id: uptime_human
    icon: mdi:clock-start
    internal: true
# Here we calc and include to the firmware a power and dose values of ionizing radiation as sensor outputs
sensor:
  - platform: uptime
    name: ${device_name} uptime
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();

  - platform: pulse_counter
    pin: GPIO27
    unit_of_measurement: 'μSv/h'
    name: '${device_name} dávkový příkon'
    count_mode: 
      rising_edge: DISABLE
      falling_edge: INCREMENT
    update_interval: 60s
    accuracy_decimals: 3
    id: my_dose_meter
    filters:
      - sliding_window_moving_average: # 5-minutes moving average (MA5) here
          window_size: 5
          send_every: 5      
      - multiply: 0.0057 # SBM20 tube conversion factor of pulses into mkSv/Hour 
  
  - platform: pulse_counter
    name: internal_pulse
    pin: GPIO27
    internal: true
    update_interval: 0ms 
    filters:
      # LogE because we want it to stand out on the console while testing
      - lambda: |-
          static int num_zeros = 0;
          if (x > 0) {
            // reset
            num_zeros = 0;
            // Indicate that a non 0 measurement was taken and dump to console
            // This is where a beeper would be fired off if desired...
            id(pulse_detect).publish_state(1);
          //  ESP_LOGE("filterLambda", "raw is %f", x );
          } else {
            num_zeros++;
          }
          // Dont spam console
          /*if (num_zeros % 1000 == 0) {
            ESP_LOGI("filterLambda", "num_zeros is %d", num_zeros );
          }*/
          return x;         

  - platform: pulse_counter
    pin: GPIO27
    unit_of_measurement: 'CPM'
    name: '${device_name} částic/min'
    id: cpm
    count_mode: 
      rising_edge: DISABLE
      falling_edge: INCREMENT
    update_interval: 30s

  - platform: integration
    name: "${device_name} Celková dávka záření"
    id: dose
    unit_of_measurement: "μSv"
    sensor: my_dose_meter # link entity id to the pulse_counter values above
    icon: "mdi:radioactive"
    accuracy_decimals: 5
    time_unit: min # integrate values every next minute
    filters:
      # obtained dose. Converting from mkSv/hour into mkSv/minute: [mkSv/h / 60] OR [mkSv/h * 0.0166666667]. 
      # if my_dose_meter in CPM, then [0.0054 / 60 minutes] = 0.00009; so CPM * 0.00009 = dose every next minute, mkSv.
      - multiply: 0.0166666667  

#   - platform: pulse_counter
#     pin: GPIO36
#     name: "esp-geiger01 geiger counter CPM" 
#     id: "geiger_counter"
#     update_interval: 60s
#     unit_of_measurement: 'CPM'
#     on_raw_value:
#       - sensor.template.publish:
#           id: radiation_level
#           state: !lambda 'return x *0.0081;'
# # # this was what I got for my data sheet and it matched reasonably well with the background data that I have.  Many people are using other values.
          
#   - platform: template
#     name: "esp-geiger01 Radiation Level"
#     id: "radiation_level"
#     unit_of_measurement: 'µSv/h'
#     update_interval: 60s
#     icon: mdi:radioactive
#     accuracy_decimals: 5

###############################################################################################
   

# sensor:
  - platform: wifi_signal
    id: wifisignal
    name: "WiFi Signal Sensor"
    update_interval: 1s # default 60s, neni nejlepší nápad nechat internal:false a nízký update interval - zbytečný provoz mqtt
    internal: true
    on_value: 
      then:
        - component.update: mydisplay

  - platform: adc
    pin: 34
    attenuation: 11db
    name: ${device_name} VBatt
    id: vcc
    update_interval: 10s
    filters:
    - multiply: 2.0  # The voltage divider requires us to multiply by 2 (100k+100k)
    device_class: voltage
    accuracy_decimals: 2
    unit_of_measurement: "V"
    on_value: 
      - component.update: bat_pct
      - component.update: mydisplay

  - platform: template
    name: "${device_name} procent baterie"
    id: bat_pct
    update_interval: never
    accuracy_decimals: 0
    unit_of_measurement: "%"
    device_class: battery
    lambda: return id(vcc).state ;
    filters:
      - calibrate_polynomial:
         degree: 3
         datapoints:
          - 0.00 -> 0.0
          - 2.97 -> 0.0 #umře
          # - 3.15 -> 0.0 #nestartuje občas spolehlivě
          # - 3.20 -> 0.0
          - 3.28 -> 10.0
          - 3.33 -> 20.0
          - 3.40 -> 30.0
          - 3.48 -> 40.0
          - 3.57 -> 50.0
          - 3.65 -> 60.0
          - 3.72 -> 70.0
          - 3.80 -> 80.0
          - 3.88 -> 90.0
          - 4.15 -> 95.0
          - 4.20 -> 100.0
      - lambda: |-
          if (x <= 100) {
            return x;
          } else {
            return 100;
          } 

graph:
  # Show bare-minimum auto-ranged graph
  - id: dose_graph
    duration: 50min
    x_grid: 1min
    y_grid: 10
    width: 152
    height: 60
    # min_value: 20
    # max_value: 60
    traces:
      - sensor: cpm
        line_type: SOLID
        line_thickness: 2
        color: color_green


font:
  - file: "fonts/arial.ttf"
    glyphs: "!%()+,-/_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzμčáůýř"
    id: pismo
    size: 14  
  - file: "fonts/arial.ttf"
    glyphs: "!%()+,-/_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzμčáůýř"
    id: pismo40
    size: 40
  - file: "fonts/arial.ttf"
    glyphs: "!%()+,-/_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzμčáůýř"
    id: pismo26
    size: 26
  - file: "fonts/arial.ttf"
    glyphs: "!%()+,-/_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzμčáůýř"
    id: pismo20
    size: 20


  - file: "fonts/materialdesignicons-webfont.ttf"
    id: radioactive
    size: 60
    glyphs: [
      '󰐼', # F043C mdi-radioactive
      '󱡞'  # F185E mdi-radioactive-circle-outline
    ]

  - file: "fonts/materialdesignicons-webfont.ttf"
    id: ikony
    size: 16
    glyphs: [

      # Wifi
      '󰤯', # F092F mdi-wifi-strength-outline
      '󰤟', # F091F mdi-wifi-strength-1
      '󰤢', # F0922 mdi-wifi-strength-2
      '󰤥', # F0925 mdi-wifi-strength-3
      '󰤨', # F0928 mdi-wifi-strength-4
      
      # Battery
      '󱃍', # F10CD mdi-battery-alert-variant-outline
      '󰁺', # F007A mdi-battery-10
      '󰁻', # F007B mdi-battery-20
      '󰁼', # F007C mdi-battery-30
      '󰁽', # F007D mdi-battery-40
      '󰁾', # F007E mdi-battery-50
      '󰁿', # F007F mdi-battery-60
      '󰂀', # F0080 mdi-battery-70
      '󰂁', # F0081 mdi-battery-80
      '󰂂', # F0082 mdi-battery-90
      '󰁹', # F0079 mdi-battery 100

      '󰢟', # F089F mdi-battery-charging-outline
      '󰢜', # F089C mdi-battery-charging-10
      '󰂆', # F0086 mdi-battery-charging-20
      '󰂇', # F0087 mdi-battery-charging-30
      '󰂈', # F0088 mdi-battery-charging-40
      '󰢝', # F089D mdi-battery-charging-50
      '󰂉', # F0089 mdi-battery-charging-60
      '󰢞', # F089E mdi-battery-charging-70
      '󰂊', # F008A mdi-battery-charging-80
      '󰂋', # F008B mdi-battery-charging-90
      '󰂅', # F0085 mdi-battery-charging-100

      # Button
      '󱊨', # mdi-gesture-tap-button
      '󰵷', # mdi-gesture-tap-hold
      '󰆙', # F0199 mdi-counter
      '󰺟', # F0E9F mdi-electric-switch
      '󱃙', # F10D9 mdi-electric-switch-closed
      '󰑊', # F044A mdi-record
      '󰻂', # F0EC2 mdi-record-circle
      '󰻃', # F0EC3 mdi-record-circle-outline
      '󰐽', # F043D mdi-radiobox-blank
      '󰐾', # F043E mdi-radiobox-marked


      # MQTT connection
      '󰌘', # F0318 mdi-lan-connect
      '󰌙', # F0319 mdi-lan-disconnect

      # OTA
      '󱍾', # F137E mdi-auto-download
      '󰇚', # F01DA mdi-download

      # Spanek
      '󰒲', # F04B2 mdi-sleep
      '󰒳', # F04B3 mdi-sleep-off

      ]

  - file: 'fonts/slkscr.ttf'
    id: font_slkscr_8
    size: 8
    glyphs: "!%()+,-/_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzμčáůýř"

  - file: 'fonts/BebasNeue-Regular.ttf'
    id: font_bebas_48
    size: 48

  - file: 'fonts/BebasNeue-Regular.ttf'
    id: font_bebas_56
    size: 56

  - file: 'fonts/arial.ttf'
    id: font_arial_12
    size: 12
  
color:
  - id: color_red
    red: 1
    green: 0
    blue: 0
  - id: color_green
    red: 0
    green: 1
    blue: 0
  - id: color_blue
    red: 0
    green: 0
    blue: 1
  - id: color_orange
    red: 1
    green: 0.6
    blue: 0
  - id: color_teal_blue
    red: 0
    green: 0.5
    blue: 0.45
  - id: color_white
    red: 1
    green: 1
    blue: 1

# TODO brightness
display:
  - platform: st7789v
    model: TTGO TDisplay 135x240
    backlight_pin: GPIO4
    cs_pin: GPIO5
    dc_pin: GPIO16
    reset_pin: GPIO23
    rotation: 90°
    #brightness: 0.5
    id: mydisplay
    update_interval: never
    lambda: |-
      int x, y, zmod;
      //radiactivity icon
      //x=240/2, y = 134/2;
      //x=56, y = 56;
      x=240, y = 135;
      //it.print(x, y, id(radioactive), id(color_green), TextAlign::CENTER, "󰐼");
      //it.printf(x, y, id(radioactive), id(pulse_input).state ? id(color_red) : id(color_green), TextAlign::CENTER, "%s", "󰐼" );
      it.printf(x, y, id(radioactive), id(pulse_detect).state ? id(color_red) : id(color_green), TextAlign::BOTTOM_RIGHT, "%s", "󰐼" );
      //zobrazena castice, vynuluj
      if (id(pulse_detect)) {id(pulse_detect).publish_state(0);}
      if (id(vcc).has_state()) {
        it.printf(24, 4, id(pismo), id(color_teal_blue), "%.2f VBat (%.2f %%)", id(vcc).state, id(bat_pct).state);
      }
      //it.print(212, 4, id(font_slkscr_8), id(color_teal_blue), "MICRO");
      //it.print(190, 120, id(font_slkscr_8), id(color_teal_blue), "TV CAMA");

      //graf
      it.graph(10, 20, id(dose_graph), color_orange);

      x=240, y=4;
      it.printf(x,y, id(pismo40), id(color_white), TextAlign::TOP_RIGHT, "%.0lf", id(cpm).state);
      x=240, y=46;
      it.print(x,y, id(pismo26), id(color_white), TextAlign::TOP_RIGHT, "cpm");
      x=4, y=80;
      //Ma5: 
      it.printf(x,y, id(pismo26), id(color_white), "%.5f μSv/h", id(my_dose_meter).state);
      x=4, y=110;
      //it.printf(x,y, id(pismo), id(color_white), "%.3f μSv", id(dose).state);
      x=4, y=115;
      it.printf(x,y, id(pismo20), id(color_white), "uptime: %s", (id(uptime_human).state).c_str());
      //it.print(x,y, id(pismo), id(color_white), (id(uptime_human).state).c_str());

      // WiFi Signal Strenght 
      if(id(wifisignal).has_state()) {
        // it.printf(0,10, id(pismo), " %.0f db", id(wifisignal).state);
        x = 0, y = 0;
        if (id(wifisignal).state >= -50) {
            //Excellent
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "󰤨");
          //  ESP_LOGI("WiFi", "Exellent");
        } else if (id(wifisignal).state  >= -60) {
            //Good
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "󰤥");
          //  ESP_LOGI("WiFi", "Good");
        } else if (id(wifisignal).state  >= -67) {
            //Fair
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "󰤢");
          //  ESP_LOGI("WiFi", "Fair");
        } else if (id(wifisignal).state  >= -70) {
            //Weak
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "󰤟");
          //  ESP_LOGI("WiFi", "Weak");
        } else {
            //Unlikely working signal
            it.print(x, y, id(ikony), TextAlign::TOP_LEFT, "󰤯");
          //  ESP_LOGI("WiFi", "Unlikely");
        }
      }

#   // ted oddelit linkou a vykreslit horni stavovy radek #####################################################################

#   it.line(0, 16, 63, 16);


#   /* Battery Voltage Discharging */
#   if(id(bat_pct).has_state() && !(id(${device_name}_usb_zapojeno).state) ) {
#     x = 63, y = 0;
#     if (id(bat_pct).state >= 97) {
#         // 100 %
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁹");
#     } else if (id(bat_pct).state  >= 90) {
#         // 90 %
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂂");
#     } else if (id(bat_pct).state  >= 80) {
#         // 80%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂁");
#     } else if (id(bat_pct).state  >= 70) {
#         // 70%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂀");
#     } else if (id(bat_pct).state  >= 60) {
#         // 60%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁿");
#     } else if (id(bat_pct).state  >= 50) {
#         // 50%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁾");
#     } else if (id(bat_pct).state  >= 40) {
#         // 40%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁽");
#     } else if (id(bat_pct).state  >= 30) {
#         // 30%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁼");
#     } else if (id(bat_pct).state  >= 20) {
#         // 20%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁻");
#     } else if (id(bat_pct).state  >= 10) {
#         // 10%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰁺");
#     } else {
#         // 0%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󱃍");
#     }
#   }
#   /* Battery Voltage Charging */
#   if(id(bat_pct).has_state() && (id(${device_name}_usb_zapojeno).state)) {
#     x = 63, y = 0;
#     if (id(bat_pct).state >= 97) {
#         // 100 %
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂅");
#     } else if (id(bat_pct).state  >= 90) {
#         // 90 %
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂋");
#     } else if (id(bat_pct).state  >= 80) {
#         // 80%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂊");
#     } else if (id(bat_pct).state  >= 70) {
#         // 70%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰢞");
#     } else if (id(bat_pct).state  >= 60) {
#         // 60%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂉");
#     } else if (id(bat_pct).state  >= 50) {
#         // 50%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰢝");
#     } else if (id(bat_pct).state  >= 40) {
#         // 40%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂈");
#     } else if (id(bat_pct).state  >= 30) {
#         // 30%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂇");
#     } else if (id(bat_pct).state  >= 20) {
#         // 20%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰂆");
#     } else if (id(bat_pct).state  >= 10) {
#         // 10%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰢜");
#     } else {
#         // 0%
#         it.print(x, y, id(ikony), TextAlign::TOP_RIGHT, "󰢟");
#     }
#   }
      

do secrets.yaml:

Kód: Vybrat vše

safecastapi_key: API_Key_získaný_na_stránce_https://api.safecast.org/
do configuration.yaml:

Kód: Vybrat vše

input_text:
  safecastapi_key:
    initial: !secret safecastapi_key
    mode: password

rest_command:    
  #Safecast
  #Device_id: aneb typ zařízení https://api.safecast.org/en-US/devices?page=10
  send_radiation_safecast_cpm:
    url: https://api.safecast.org/measurements.json?api_key={{states("input_text.safecastapi_key")}}
    content_type: "application/json; charset=utf-8"
    method: POST
    payload: >-
      {"longitude": "{{('%0.4f'|format(state_attr('zone.home', 'longitude')))}}","latitude": "{{('%0.6f'|format(state_attr('zone.home', 'latitude')))}}","device_id": "256","value": "{{states("sensor.geiger_castic_min")}}","unit": "cpm","captured_at":"{{ (now().isoformat())}}"}
  send_radiation_safecast_usv:
    url: https://api.safecast.org/measurements.json?api_key={{states("input_text.safecastapi_key")}}
    content_type: "application/json; charset=utf-8"
    method: POST
    payload: >-
      {"longitude": "{{('%0.4f'|format(state_attr('zone.home', 'longitude')))}}","latitude": "{{('%0.6f'|format(state_attr('zone.home', 'latitude')))}}","device_id": "256","value": "{{states("sensor.geiger_davkovy_prikon")}}","unit": "usv","captured_at":"{{ (now().isoformat())}}"}

kód automatizace: (po restartu)

Kód: Vybrat vše

alias: CRON-safecast
description: radiation
trigger:
  - platform: time_pattern
    minutes: /1
condition:
  - condition: and
    conditions:
      - condition: numeric_state
        entity_id: sensor.geiger_castic_min
        above: 10
      - condition: numeric_state
        entity_id: sensor.geiger_uptime
        above: 60
action:
  - service: rest_command.send_radiation_safecast_cpm
    data: {}
  - service: rest_command.send_radiation_safecast_usv
    data: {}
mode: single
Vše co si přinesu domů je buď Shelly, nebo to skončí buď pod ESPhome nebo pod Zigbee2mqtt.
Ajťák co pamatuje BBS a OS/2 Warp a je mu jedno o jaký systém nebo síťařinu běží.
HA OS jako jedna z Proxmox VM na Odroid H3+/64GB https://github.com/tteck/Proxmox

Odpovědět

Zpět na „Komplexní projekty“