IOT Geiger Counter (InfluxDB)

Of course every household needs a Geiger Counter and I bought this kit to do all the fancy 400/500V voltage work along with an SBM-20 Geiger-Muller Tube on eBay for the actual radiation detecting. Typically it seems people hook this up to a tablet etc. and run an app but my plan was to log to InfluxDB. It can also operate stand-alone which is why I added a handy display (Nokia 5110).

Finished IOT Geiger Counter

Notes:
This type of detector is designed to detect Beta & Gamma rays. (it cannot detect Alpha rays but this sensor can be added easily if wanted.

What it does:
– Listens & counts pulses for 60 seconds
– After 60 seconds writes this value to InfluxDB
– Updates display with current metrics. (last 60 second reading, average reading, max reading, estimated dosage & the current IP address)

Connecting it up:

LCD PinESP8266 Labelled PinESP8266 GPIO PinGeiger Detector Board
1 – RSTD0GPIO 16
2 – CED1GPIO 5
3 – DCD2GPIO 4
4 – DIND3GPIO 0
5 – CLKD4GPIO 2
6 – Vcc3.3V3.3V
7 – Backlight3.3V (on)3.3V
8 – GroundGroundGroundGround
D5GPIO 14Int (interrupt)
VU/ 5VVU / 5V5V

Code:
See latest code at my GitHub (or below):
– Requires Adafruit libraries. Link 1, Link 2
– Requires Running Average Library
– Requires InfluxDb library

#include <ESP8266WiFi.h>
#include <InfluxDb.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include "RunningAverage.h"

#define INFLUXDB_HOST "192.168.1.XXX"
#define WIFI_SSID "XXXXXXXXXXXX"
#define WIFI_PASS "XXXXXXXXXXXX"
#define DATABASE "XXXXXXXXXXXX"
#define MEASUREMENT "XXXXXXXXXXXX"
#define DEVICE "XXXXXXXXXXXX"
#define ID "Geiger_Counter"
#define LOG_PERIOD 60000

Influxdb influx(INFLUXDB_HOST);
Adafruit_PCD8544 display = Adafruit_PCD8544(2, 0, 4, 5, 16); //LCD 1.5 Inch (Nokia 5110)84×48 (x,y) pixels
RunningAverage raMinute(60);

//################
int debug = 1; //#
//################

int loopCount = 0;
int cpm = 0;
int cpm_max = 0;
int cpm_1hr_avg = 0;
int cpm_ravg = 0;
int counts = 0;
int cal_factor = 1;
int wifiStatus;

unsigned long currentMillis;
unsigned long previousMillis; //variable for time measurement

void setup(){                                               
  Serial.begin(9600);      // start serial monitor
  delay(1000);
  Serial.println("");
  Serial.println("");
  Serial.println("Setup Routine of ESP8266 Geiger Counter");

  display.begin();
  display.setContrast(55);
  display.display();        // show adafruid splashscreen
  delay(2000);
  display.clearDisplay();   // clears the screen and buffer

  pinMode(LED_BUILTIN, OUTPUT); //D4
  digitalWrite(LED_BUILTIN, HIGH); //Turns it off

  pinMode(14, INPUT_PULLUP);                  // set pin INT0 input for capturing GM Tube events / GPIO5 = D1
  attachInterrupt(14, tube_pulse, FALLING); //defines interrupts

  raMinute.clear();

  influx.setDb(DATABASE);

  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.setCursor(0,0); display.setTextSize(1);display.print("Up Hrs: ");display.setCursor(48,0);display.print("0");
  display.setCursor(0,9); display.setTextSize(1);display.print("CPMi 1m:");
  display.setCursor(0,17);display.setTextSize(1);display.print("CPM avg:");
  display.setCursor(0,25);display.setTextSize(1);display.print("CPM Max:");
  display.setCursor(0,33);display.setTextSize(1);display.print("uSv/hr: ");
  display.setCursor(0,41);display.setTextSize(1);display.print("Not Connected");
  display.display();

    wifiStatus = WiFi.status();
    if (wifiStatus != WL_CONNECTED) {   
      new_connection();
    }
    else {
        display.fillRect(0,41,84,48, WHITE);
        display.setCursor(0,41);
        display.print(WiFi.localIP());
        display.display();
    }
    

  if (debug == 1) {Serial.println("Setup Complete.");}
}

void loop(){                                              
  currentMillis = millis();
  if(currentMillis - previousMillis > LOG_PERIOD){
    cpm = counts * cal_factor;                        

    raMinute.addValue(cpm);
    cpm_ravg = raMinute.getAverage();

    if (cpm > cpm_max){
      cpm_max = cpm;
    }
    
    Serial.print("CPM: ");                         
    Serial.println(cpm);                          

    display.fillRect(48,0,40,40, WHITE);
    display.display();
    display.setCursor(48,0);display.print(loopCount*.0166);
    display.setCursor(48,9);display.print(cpm);
    display.setCursor(48,17);display.print(cpm_ravg);
    display.setCursor(48,25);display.print(cpm_max);
    display.setCursor(48,33);display.print(cpm_ravg*0.0057);
    display.display();

    Serial.println("Attempting to write to DB");   
    counts = 0;

    InfluxData row(MEASUREMENT);
    row.addTag("Device", DEVICE);
    row.addTag("ID", ID);
    row.addValue("CPM", cpm);  
    row.addValue("LoopCount", loopCount);
    row.addValue("RandomValue", random(0, 100));
  
    wifiStatus = WiFi.status();
    while ( wifiStatus != WL_CONNECTED )
        {
          new_connection();
        }
  
    influx.write(row);
    if (debug == 1) {Serial.println("Wrote Data.");}
  
    //WiFi.mode(WIFI_OFF); // Probably turn off Wifi if want to save battery
    //WiFi.forceSleepBegin();
    //delay( 1 );
  
    status_blink();
    previousMillis = currentMillis;
    loopCount++;
  }
}

ICACHE_RAM_ATTR        //Needed to fix ISR not in IRAM boot error
void tube_pulse(){     //procedure for capturing events from interrupt
  counts++;
}

void new_connection() {
  
    wifiStatus = WiFi.status();
    
    if (wifiStatus != WL_CONNECTED) {   
       
        WiFi.mode(WIFI_STA);
        WiFi.begin(WIFI_SSID, WIFI_PASS);
        int loops = 0;
        int retries = 0;
        display.fillRect(0,41,84,48, WHITE);
        display.setCursor(0,41);
        display.print("Not Connected");
        display.display();
       
        while (wifiStatus != WL_CONNECTED)
        {
          retries++;
          if( retries == 300 )
          {
              if (debug == 1) {Serial.println( "No connection after 300 steps, powercycling the WiFi radio. I have seen this work when the connection is unstable" );}
              WiFi.disconnect();
              delay( 10 );
              WiFi.forceSleepBegin();
              delay( 10 );
              WiFi.forceSleepWake();
              delay( 10 );
              WiFi.begin( WIFI_SSID, WIFI_PASS );
          }
          if ( retries == 600 )
          {
              if (debug == 1) {Serial.println( "No connection after 600 steps. WiFi connection failed, disabled WiFi and waiting for a minute" );}
              WiFi.disconnect( true );
              delay( 1 );
              WiFi.mode( WIFI_OFF );
              WiFi.forceSleepBegin();
              delay( 10 );
              retries = 0;
              
              if( loops == 3 )
              {
                  if (debug == 1) {Serial.println( "That was 3 loops, still no connection so let's go to deep sleep for 2 minutes" );}
                  Serial.flush();
                  ESP.deepSleep( 120000000, WAKE_RF_DISABLED );
              }     
          }
          delay(50);
          wifiStatus = WiFi.status();
        }
        
        wifiStatus = WiFi.status();
        Serial.print("WiFi connected, IP address: ");Serial.println(WiFi.localIP());
        display.fillRect(0,41,84,48, WHITE);
        display.setCursor(0,41);
        display.print(WiFi.localIP());
        display.display();
    }
}

void status_blink() {
  digitalWrite(LED_BUILTIN, LOW);   // Turn the LED on (Note that LOW is the voltage level   
  delay(100);
  digitalWrite(LED_BUILTIN, HIGH);   // Turn the LED on (Note that LOW is the voltage level
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);   // Turn the LED on (Note that LOW is the voltage level   
  delay(100);
  digitalWrite(LED_BUILTIN, HIGH);   // Turn the LED on (Note that LOW is the voltage level
}

ToDo:
– Comment code
– Add in check at start of code to see if tube functioning.
– I would like to update the running average to 60min average but not enough time to currently do these 10 lines of code.
– Add in control (on/off ) for the LCD backlight, buzzer & Wifi for battery consumption.
– Perhaps would be nice to log to SD card also, not sure if I still have enough I/O for that.
– The ‘case’ is a very rough and not worthy of sharing, a nicer more bespoke would be ideal.
– Add radiation symbol on splash screen.

Resources I used:
https://mightyohm.com/blog/2014/11/a-spotters-guide-to-the-sbm-20-geiger-counter-tube/

That’s it!

ESP8266 & Stepper Motors

Nothing outrageously complicated but when I used the default arduino stepper library to control my 28BYJ-48 stepper motors on my ESP8266 they only turned one direction. With modifications to the library it was possible to get one stepper motor working correctly but the ESP crashed if I tried to control 2 stepper motors, solution sketch below:

What did work:
– Sketch below. (Note: No fancy acceleration/deceleration but I didnt need it)

int pos_rot = 0;  
int pos_elev = 0; 

int rot_counter = 0;
int elev_counter = 0;

int next_rot = -1;
int next_elev = 1;

int step_delay = 10; //Delay between steps in ms

const int motor_pin_1 = 16; // ESP D0
const int motor_pin_2 = 5;  // ESP D1
const int motor_pin_3 = 4;  // ESP D2
const int motor_pin_4 = 0;  // ESP D3
const int motor_pin_5 = 2;  // ESP D4
const int motor_pin_6 = 14; // ESP D5
const int motor_pin_7 = 12; // ESP D6
const int motor_pin_8 = 13; // ESP D7

void setup() {
  pinMode(motor_pin_1, OUTPUT); // Blue
  pinMode(motor_pin_2, OUTPUT); // Pink
  pinMode(motor_pin_3, OUTPUT); // Yellow
  pinMode(motor_pin_4, OUTPUT); // Orange
  pinMode(motor_pin_5, OUTPUT);
  pinMode(motor_pin_6, OUTPUT);
  pinMode(motor_pin_7, OUTPUT);
  pinMode(motor_pin_8, OUTPUT);
  
  // Begin Serial communication at a baud rate of 9600:
  Serial.begin(115200);
  delay(100);Serial.println("Leaving Setup");delay(100);
}

void loop() {
    //test_sweep();
    rot(2038);delay(2000);rot(-2038);   // 2038 steps clockwise followed by the same counterclockwise
    elev(1019);delay(2000);elev(-1019); // 1019 steps clockwise followed by the same counterclockwise
}

void test_sweep(){
      Serial.print("Rotating CCW, pos_rot = ");Serial.println(pos_rot);

    while (pos_rot < 2038) {
    rot(1);
    pos_rot = pos_rot + 1;
 
        if ((pos_rot < 1019) && ((pos_rot % 2) == 0)) {
          elev(2);          
        }
        else if ((pos_rot > 1019) && ((pos_rot % 2) == 0)) {
          elev(-2);
        }
  }
    while (pos_rot > 0) {
    rot(-1);
    pos_rot = pos_rot - 1;
 
        if ((pos_rot > 1019) && ((pos_rot % 2) == 0)) {
          elev(2);
        }
        else if ((pos_rot < 1019) && ((pos_rot % 2) == 0)) {
          elev(-2);
        }
  }
}

void elev(int num_steps) {
    if (num_steps > 0) {
        while (elev_counter < num_steps) {        
          elev_step(next_elev);
          next_elev = next_elev +1;
          if (next_elev > 7) { next_elev = 0;}
          elev_counter = elev_counter +1;
        }
    }
    else {
        num_steps = abs(num_steps);
        while (elev_counter < num_steps) {        
          elev_step(next_elev);
          next_elev = next_elev - 1;
          if (next_elev < 0) { next_elev = 7;}
          elev_counter = elev_counter +1;
        }    
    }
    elev_counter = 0;
}

void rot(int num_steps) {
    if (num_steps > 0) {
    
        while (rot_counter < num_steps) {        
          rot_step(next_rot);
          next_rot = next_rot +1;
          if (next_rot > 7) { next_rot = 0;}
          rot_counter = rot_counter +1;
        }
    }
    else {
        num_steps = abs(num_steps);
        while (rot_counter < num_steps) {        
          rot_step(next_rot);
          next_rot = next_rot - 1;
          if (next_rot < 0) { next_rot = 7;}
          rot_counter = rot_counter +1;
        }    
    }

    rot_counter = 0;  
}

void rot_step(int go_step) {
  switch (go_step) {
      case 0:  // 0001
        digitalWrite(motor_pin_1, 0);  // Blue
        digitalWrite(motor_pin_2, 0);  // Pink
        digitalWrite(motor_pin_3, 0);  // Yellow
        digitalWrite(motor_pin_4, 1);  // Orange
      break;
      case 1:  // 0011
        digitalWrite(motor_pin_1, 0);
        digitalWrite(motor_pin_2, 0);
        digitalWrite(motor_pin_3, 1);
        digitalWrite(motor_pin_4, 1);
      break;
      case 2:  //0010
        digitalWrite(motor_pin_1, 0);
        digitalWrite(motor_pin_2, 0);
        digitalWrite(motor_pin_3, 1);
        digitalWrite(motor_pin_4, 0);
      break;
      case 3:  //0110
        digitalWrite(motor_pin_1, 0);
        digitalWrite(motor_pin_2, 1);
        digitalWrite(motor_pin_3, 1);
        digitalWrite(motor_pin_4, 0);
      break;
      case 4:  // 0100
        digitalWrite(motor_pin_1, 0);
        digitalWrite(motor_pin_2, 1);
        digitalWrite(motor_pin_3, 0);
        digitalWrite(motor_pin_4, 0);
      break;
      case 5:  // 1100
        digitalWrite(motor_pin_1, 1);
        digitalWrite(motor_pin_2, 1);
        digitalWrite(motor_pin_3, 0);
        digitalWrite(motor_pin_4, 0);
      break;
      case 6:  //1000
        digitalWrite(motor_pin_1, 1);
        digitalWrite(motor_pin_2, 0);
        digitalWrite(motor_pin_3, 0);
        digitalWrite(motor_pin_4, 0);
      break;
      case 7:  //1001
        digitalWrite(motor_pin_1, 1);
        digitalWrite(motor_pin_2, 0);
        digitalWrite(motor_pin_3, 0);
        digitalWrite(motor_pin_4, 1);
      break;
    }
    delay(step_delay);
}

void elev_step(int go_step) {
  //Serial.println(go_step);
  switch (go_step) {
      case 0:  // 0001
        digitalWrite(motor_pin_5, LOW);
        digitalWrite(motor_pin_6, LOW);
        digitalWrite(motor_pin_7, LOW);
        digitalWrite(motor_pin_8, HIGH);
      break;
      case 1:  // 0011
        digitalWrite(motor_pin_5, LOW);
        digitalWrite(motor_pin_6, LOW);
        digitalWrite(motor_pin_7, HIGH);
        digitalWrite(motor_pin_8, HIGH);
      break;
      case 2:  //0010
        digitalWrite(motor_pin_5, LOW);
        digitalWrite(motor_pin_6, LOW);
        digitalWrite(motor_pin_7, HIGH);
        digitalWrite(motor_pin_8, LOW);
      break;
      case 3:  //0110
        digitalWrite(motor_pin_5, LOW);
        digitalWrite(motor_pin_6, HIGH);
        digitalWrite(motor_pin_7, HIGH);
        digitalWrite(motor_pin_8, LOW);
      break;
      case 4:  // 0100
        digitalWrite(motor_pin_5, LOW);
        digitalWrite(motor_pin_6, HIGH);
        digitalWrite(motor_pin_7, LOW);
        digitalWrite(motor_pin_8, LOW);
      break;
      case 5:  // 1100
        digitalWrite(motor_pin_5, HIGH);
        digitalWrite(motor_pin_6, HIGH);
        digitalWrite(motor_pin_7, LOW);
        digitalWrite(motor_pin_8, LOW);
      break;
      case 6:  //1000
        digitalWrite(motor_pin_5, HIGH);
        digitalWrite(motor_pin_6, LOW);
        digitalWrite(motor_pin_7, LOW);
        digitalWrite(motor_pin_8, LOW);
      break;
      case 7:  //1001
        digitalWrite(motor_pin_5, HIGH);
        digitalWrite(motor_pin_6, LOW);
        digitalWrite(motor_pin_7, LOW);
        digitalWrite(motor_pin_8, HIGH);
      break;
    }
    delay(step_delay);
}

That’s it!

Capacitive Touch Hack (for Fluval Edge Aquarium Control)

This post is about automating the light on the Fluval Edge Aquarium but can be applied to any capacitive touch button I imagine. It turns the light on at 10am in the morning, turns it blue at 8pm and finally turns it off at midnight. It has real time clock (RTC) to keep track of time and can recover from power outages due to storing current state in EEPROM.

My first attempt was to have passive control by creating capacitance on a piece of aluminium foil/tape and manipulating this to activate the sensor but I could not get it working so the not so elegant solution is to have a servo ‘touch’ the sensor on a schedule.

Video of it in action:

Parts Required:
Arduino (any), I used Nano.
RTC (any), I used DS1307.
Servo (any), I used HK15178 10g servo.

Connections: (aside from power which are all 5V)
Arduino A4 -> RTC SDA
Arduino A5 -> RTC SCL
Arduino D3 -> Servo Control (was yellow wire for me)

Paste of Arduino code below, don’t forget to add the RTC library, hosted here if you don’t have it already.

#include <Wire.h>
#include "RTClib.h"
#include <EEPROM.h>
#include <Servo.h>

Servo myservo;  // create servo object to control a servo 

RTC_DS1307 RTC;

char receivedChar;
boolean newData = false;
int pos_standby = 180;    // variable to store the servo position 
int pos_active = 100;
int led = 13;
int mode = 1;             //1 for PROD, 2 for DEV (Serial Input
int bulb_status = 0;
int starttime = 0;
int endtime = 0;
int loopcount = 0;
int address = 12;
byte value;

void setup() { 
  Serial.begin(9600);
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
  }
  
  read_eeprom();              // Gets current position from EEPROM
  myservo.attach(3);          // Attaches the servo on pin 3 to the servo object 
  myservo.write(pos_standby); // Puts servo in default position
  Serial.println("Setup Complete");

  //#### Uncomment the below to set the RTC to the date & time this sketch was compiled ###
  //#### Then comment it out again and reupload sketch to Arduino ###  
  //RTC.adjust(DateTime(__DATE__, __TIME__));
}

void loop() {
  print_time();
  delay(500); 
  while (true){
   
      if (mode == 1) {
        bulb_sequence();
        }      
  
      starttime = millis();
      endtime = starttime;
      while (((endtime - starttime) <=10000) || (loopcount < 10000)) // do this loop for up to 10000mS
        {
        loopcount = loopcount+1;
        endtime = millis();
        }  
        
   recvOneChar();
   showNewData();

    if (receivedChar=='a') {
    Serial.println("A Selected");
    servo_move(1); //Bulb ON
    }  
    else if (receivedChar=='b') {
    Serial.println("B Selected");
    servo_move(2); //Bulb BLUE
    }     
    else if (receivedChar=='c') {
    Serial.println("C Selected");
    servo_move(3); //Bulb OFF
    }    
   receivedChar='d';
        
  }
}

void recvOneChar() {
 if (Serial.available() > 0) {
 receivedChar = Serial.read();
 newData = true;
 }
}

void showNewData() {
 if (newData == true) {
 //Serial.print("This just in ... ");
 //Serial.println(receivedChar);
 newData = false;
 }
}

void bulb_sequence() {
      DateTime now = RTC.now();
      if (now.hour() > 10 && now.hour() < 20 && bulb_status!=1) {
          servo_move(1); //Bulb ON
          }
      else if (now.hour() > 19 && now.hour() < 23 && bulb_status!=2) {
          servo_move(2); //Bulb BLUE
          }
      else if (now.hour()>=23 && bulb_status!=3) {
          servo_move(3); //Bulb OFF
          }   
          //Serial.println(bulb_status); 
          //Serial.println(now.hour);     
}

void servo_move(int x) 
{ 
  read_eeprom();
  while (bulb_status != x) {
    servo(); 
  }
  write_eeprom();   
} 

void servo() 
{ 
  myservo.write(pos_active);              // tell servo to go to position in variable 'pos' 
  delay(1000);                     // waits 1s for the servo to reach the position                       
  myservo.write(pos_standby);              // tell servo to go to position in variable 'pos' 
  delay(1000);                     // waits 1s for the servo to reach the position  
  bulb_status += 1;
  if (bulb_status == 4) {
    bulb_status = 1;
    digitalWrite(led, HIGH);  
    }
  digitalWrite(led, LOW);  
} 

void print_time() {
    DateTime now = RTC.now(); 
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
}

void read_eeprom()
{
  // read a byte from the current address of the EEPROM
  value = EEPROM.read(address);
  Serial.print("EERPROM Stored Value at Address: "); 
  Serial.print(address);
  Serial.print("\t");
  Serial.print(value, DEC);
  Serial.println();
  bulb_status = value;
}

void write_eeprom()
{
  EEPROM.write(address, bulb_status);
  Serial.print("New EEPROM: ");
  Serial.println(bulb_status);
}

That’s it!