Sunday 20 October 2019

Thunderbolts and Lightning Detector AS3935 Part 1 of 3

Bohemian Rhapsody, Queen
"Thunderbolts and lightning, very very frightening me" - Queen, Bohemian Rhapsody

Being near the equator, Malaysia is very frequently struck by lightning and has a flash rate density close to 100. ADSL broadband modems, cordless telephones, network switches, autogate controllers and monitors regularly get damaged.

A few times a year the storms get really bad: you can hear the lightning hiss followed by the thunderclap. The phone cables are fried, electric power is out and the dogs try to get on your lap, never mind the cat.

I often repair the electronics, but there is something about lightning damage: they are never quite the same. TVs seem more immune, but if you use them with a computer (as a monitor), they get damaged by lightning. Go figure.

But the most annoying damage is to the ADSL modem. This is because I run an IoT server 24/7. I have backup power via UPS but a fried modem often results in service interruption. Eventually I installed a backup 3G modem using a TP-Link MR-3420.

For some reason, things get a lot better if we simply disconnected the telephone line from the ADSL modem and phone. This causes the MR-3420 to fail over to 3G, and I put in a little fix to ensure a failback once the ADSL modem is reconnected. Things do not get fried as much maybe once a year a LAN switch or a monitor cops it.

And this works very well for us unless we are away on holiday. What we need is a system to disconnect the modem from the phone line when the storm is close and reconnect when it passes. And sure enough, cometh the hour cometh the chip: AS3935 from Austria Microsystems.

I got my AS3935 for RM92 (about USD20)

But why not stay on 3G? The problem is in Malaysia we still have a data cap on 3G, maybe 10GB per month, and if your IoT device is a security camera it quickly adds up.

Since I had good results with the SPI interface on the NodeMCU ESP-12E before,
I made the following SPI cable to the CJMCU AS3935:

PIN#   AS3935                               diagram  ESP-12E    Arduino IDE no
 1          VCC                                      5V
 2          GND                                     GND
 3          SCL                    HSCLK     D5      GPIO14                     14
 5          MOSI                  HMISO     D6      GPIO12                     12
 4          MISO                  HMOSI     D7      GPIO13                     13
 6          CS                       HCS         D8      GPIO15                     15
 7          SI                                         GND
 8          IRQ                                      D2      GPIO2                         4
 9          EN_VREG                          5V

The AS3935 allows for 3V3 to be used but the CJMCU board did not bring out VREG to the header, so I needed to supply 5V and enable the onboard regulator by tying EN_VREG to 5V. The last column, 'Arduino IDE number' is the pin number to use for the Arduino sketch.  I used a 12-way header with a 10-way ribbon cable because I wanted to use GPIO1 and GPIO3 to control a relay board.

For software, I used Eva Schindling's Thunder and Lightning. Even though it was written five years ago for the Arduino, it pretty much worked first time for the ESP-12E. I used her AS3935_example.ino sketch with just a few tweaks to accommodate my SPI cable and the ESP-12E.

Download her code in a zip file, unzip it and the make a symbolic link to your Arduino IDE library:

ln -s /home/guest/as3935/ThunderAndLightning-master/library/AS3935 /home/guest/Arduino/libraries/AS3935

And that was it. I knew Arduino SPI code did not travel very well to the ESP8266 and was well, thunderstruck it worked first time. And unlike Eva who back in 2014 seemed to have lacked good thunderstorms to test with, a big storm arrived just in time for testing.

Life indeed is good. Happy Trails.
/*
  LightningDetector.pde - AS3935 Franklin Lightning Sensor™ IC by AMS library demo code
  Copyright (c) 2012 Raivis Rengelis (raivis [at] rrkb.lv). All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 3 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <SPI.h>
#include <AS3935.h>

void printAS3935Registers();

// Function prototype that provides SPI transfer and is passed to
// AS3935 to be used from within library, it is defined later in main sketch.
// That is up to user to deal with specific implementation of SPI
// Note that AS3935 library requires this function to have exactly this signature
// and it can not be member function of any C++ class, which happens
// to be almost any Arduino library
// Please make sure your implementation of choice does not deal with CS pin,
// library takes care about it on it's own
byte SPItransfer(byte sendByte);

// Iterrupt handler for AS3935 irqs
// and flag variable that indicates interrupt has been triggered
// Variables that get changed in interrupt routines need to be declared volatile
// otherwise compiler can optimize them away, assuming they never get changed
void AS3935Irq();
volatile int AS3935IrqTriggered;

// First parameter - SPI transfer function, second - Arduino pin used for CS
// and finally third argument - Arduino pin used for IRQ
// It is good idea to chose pin that has interrupts attached, that way one can use
// attachInterrupt in sketch to detect interrupt
// Library internally polls this pin when doing calibration, so being an interrupt pin
// is not a requirement

#define IRQpin 4 // ESP-12E
#define CSpin 15

AS3935 AS3935(SPItransfer,CSpin,IRQpin);

void setup()
{
  // Serial.begin(9600);
  Serial.begin(115200); // 2019-10-19 ESP-12E default
  // first begin, then set parameters
  SPI.begin();
  // NB! chip uses SPI MODE1
  SPI.setDataMode(SPI_MODE1);
  // NB! max SPI clock speed that chip supports is 2MHz,
  // but never use 500kHz, because that will cause interference
  // to lightning detection circuit
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  // and chip is MSB first
  SPI.setBitOrder(MSBFIRST);
  // reset all internal register values to defaults
  AS3935.reset();
  // and run calibration
  // if lightning detector can not tune tank circuit to required tolerance,
  // calibration function will return false
  
  
  //if(!AS3935.calibrate())
  //  Serial.println("Tuning out of range, check your wiring, your sensor and make sure physics laws have not changed!");



  outputCalibrationValues();
  recalibrate();

  AS3935.setNoiseFloor(1);
  AS3935.setSpikeRejection(2);
  AS3935.setWatchdogThreshold(1);
  
  outputCalibrationValues();
  recalibrate();

  // since this is demo code, we just go on minding our own business and ignore the fact that someone divided by zero

  // first let's turn on disturber indication and print some register values from AS3935
  // tell AS3935 we are indoors, for outdoors use setOutdoors() function
  AS3935.setIndoors();
  // AS3935.setOutdoors();
  // turn on indication of distrubers, once you have AS3935 all tuned, you can turn those off with disableDisturbers()
  AS3935.enableDisturbers();
  // AS3935.disableDisturbers();
  printAS3935Registers();
  AS3935IrqTriggered = 0; 
  // Using interrupts means you do not have to check for pin being set continiously, chip does that for you and
  // notifies your code
  // demo is written and tested on ChipKit MAX32, irq pin is connected to max32 pin 2, that corresponds to interrupt 1
  // look up what pins can be used as interrupts on your specific board and how pins map to int numbers

  // ChipKit Max32 - irq connected to pin 2
  // attachInterrupt(1,AS3935Irq,RISING);
  // uncomment line below and comment out line above for Arduino Mega 2560, irq still connected to pin 2
  attachInterrupt(digitalPinToInterrupt(IRQpin),AS3935Irq,RISING); // ESP-12E
}

void loop()
{
  // here we go into loop checking if interrupt has been triggered, which kind of defeats
  // the whole purpose of interrupts, but in real life you could put your chip to sleep
  // and lower power consumption or do other nifty things
  if(AS3935IrqTriggered)  
  {
    // reset the flag
    AS3935IrqTriggered = 0;
    // wait 2 ms before reading register (according to datasheet?)
    delay(2);
    // first step is to find out what caused interrupt
    // as soon as we read interrupt cause register, irq pin goes low
    int irqSource = AS3935.interruptSource();
    // returned value is bitmap field, bit 0 - noise level too high, bit 2 - disturber detected, and finally bit 3 - lightning!
    if (irqSource & 0b0001)
      Serial.println("Noise level too high, try adjusting noise floor");
    if (irqSource & 0b0100)
      Serial.println("Disturber detected");
    if (irqSource & 0b1000)
    {
      // need to find how far that lightning stroke, function returns approximate distance in kilometers,
      // where value 1 represents storm in detector's near victinity, and 63 - very distant, out of range stroke
      // everything in between is just distance in kilometers
      int strokeDistance = AS3935.lightningDistanceKm();
      if (strokeDistance == 1)
        Serial.println("Storm overhead, watch out!");
      if (strokeDistance == 63)
        Serial.println("Out of range lightning detected.");
      if (strokeDistance < 63 && strokeDistance > 1)
      {
        Serial.print("Lightning detected ");
        Serial.print(strokeDistance,DEC);
        Serial.println(" kilometers away.");
      }
    }
  }
}

void printAS3935Registers()
{
  int noiseFloor = AS3935.getNoiseFloor();
  int spikeRejection = AS3935.getSpikeRejection();
  int watchdogThreshold = AS3935.getWatchdogThreshold();
  int minLightning = AS3935.getMinimumLightnings();
  Serial.print("Noise floor is: ");
  Serial.println(noiseFloor,DEC);
  Serial.print("Spike rejection is: ");
  Serial.println(spikeRejection,DEC);
  Serial.print("Watchdog threshold is: ");
  Serial.println(watchdogThreshold,DEC); 
  Serial.print("Minimum Lightning is: ");
  Serial.println(minLightning,DEC);   
}

// this is implementation of SPI transfer that gets passed to AS3935
// you can (hopefully) wrap any SPI implementation in this
byte SPItransfer(byte sendByte)
{
  return SPI.transfer(sendByte);
}

// this is irq handler for AS3935 interrupts, has to return void and take no arguments
// always make code in interrupt handlers fast and short
void ICACHE_RAM_ATTR AS3935Irq()
{
  AS3935IrqTriggered = 1;
}


void recalibrate() {
  delay(50);
  Serial.println();
  int calCap = AS3935.getBestTune();
  Serial.print("antenna calibration picks value:\t ");
  Serial.println(calCap);
  delay(50);
}

void outputCalibrationValues() {
   // output the frequencies that the different capacitor values set:
  delay(50);
  Serial.println();
  for (byte i = 0; i <= 0x0F; i++) {
    int frequency = AS3935.tuneAntenna(i);
    Serial.print("tune antenna to capacitor ");
    Serial.print(i);
    Serial.print("\t gives frequency: ");
    Serial.print(frequency);
    Serial.print(" = ");
    long fullFreq = (long) frequency*160;  // multiply with clock-divider, and 10 (because measurement is for 100ms)
    Serial.print(fullFreq,DEC);
    Serial.println(" Hz");
    delay(10);
  }
}

Wednesday 2 October 2019

mDNS: Bonjour Slackware

Bonjour Sourire by Henri Salvador
"Adieu tristesse, bonjour sourire" - Henri Salvador

Goodbye sadness, hello smile. Somehow it seems apt.

For 20 years now I have used static IP rather than a Name Server for my home network. When I started my home network, computers were expensive and few. But then came the Raspberry Pi and the Espressif ESP8266.

In practice I maintained a 'name server' by hand, updating my /etc/resolv.conf every time I added a new computer to the network. There are now over 100 entries in 3 sub-nets but you do get used to it. What put me over the edge was the IoT device.

The need for periodic security updates meant reprogramming: the device had to be taken down and the ESP8266 plugged into its programmer. The most useful ones always seem to be the most inaccessible, like upside down on the ceiling or out in the elements like the autogate controller.

Then along came Over The Air programming, just like you would update your App in your smartphone. But now every IoT device came with its unique static IP address, while I might have 10 IoT lights installed, there is only 1 version source code and there was a high chance I would brick a device by using the wrong address.

But adieu tristesse, there is mDNS or Multicast DNS to the rescue. mDNS was first implemented as Apple Inc's Bonjour. Actually I unknowingly used it in ArduinoOTA and Raspbian. It is just my main workstations ran Slackware, which did not come with mDNS pre-installed. It would be nice to update IoT devices directly from my development machine.

To install mDNS onto my Slackware 14.2-current (which does not automatically resolve dependencies) you will need to install in this order: libdaemon, avahi and  nss-mdns.

But first you need to create the user 'avahi':

$groupadd -g 214 avahi
$useradd -u 214 -g 214 -c "Avahi User" -d /dev/null -s /bin/false avahi

libdaemon:


$tar -xvpzf libdaemon.tar.gz
$cd libdaemon

Download the source code libdaemon-0.14.tar.gz into libdaemon directory and:

$./libdaemon.SlackBuild

which produced /tmp/libdaemon-0.14-x86_64-1_SBo.tgz and can be installed into Slackware thus:

$upgradepkg --install-new /tmp/libdaemon-0.14-x86_64-1_SBo.tgz

avahi:


$tar -xvpzf avahi.tar.gz
$cd avahi

Download your source code

$ ./avahi.SlackBuild
$upgradepkg --install-new /tmp/avahi-0.7-x86_64-1_SBo.tgz

In my case I ring-fenced my IoT devices in their own WiFi Access Point. Many consumer WiFi routers (like my TP-Link TL-MR3420) randomly drops connections to the WiFi clients when there are too many of them, like around 30 devices.

This means my Slackware server (ie Google smart home nodejs, MQTT, and webhook servers) can only access the IoT devices from behind their router. In this case I had to set just one of the mDNS computers in the IoT subnet to reflector. That is in /etc/avahi/avahi-daemon.conf add:

[reflector]
enable-reflector=yes
  
$/etc/rc.d/rc.avahidaemon start
Starting Avahi mDNS/DNS-SD Daemon:  /usr/sbin/avahi-daemon -D
$/etc/rc.d/rc.avahidnsconfd start

Add the following lines to your /etc/rc.d/rc.local:

# Start avahidaemon
if [ -x /etc/rc.d/rc.avahidaemon ]; then
  /etc/rc.d/rc.avahidaemon start
fi
# Start avahidnsconfd
if [ -x /etc/rc.d/rc.avahidnsconfd ]; then
  /etc/rc.d/rc.avahidnsconfd start
fi

nss-mdns:


$tar -xvzf nss-mdns.tar.gz
$cd nss-mdns

Download your source code

$./nss-mdns.SlackBuild
$upgradepkg --install-new /tmp/nss-mdns-0.10-x86_64-2_SBo.tgz

Now your  /etc/nsswitch.conf will have a line line this:

hosts:          files dns

Which you need to change to 

hosts:          files mdns4_minimal [NOTFOUND=return] dns

After which a command like getent will work:

$getent hosts MyComputer.local
123.45.67.89   MyComputer.local

And you should be able to remotely access it by name without a DNS server:

$ssh -t MyComputer.local

Even better, it works with the ArduinoOTA code for the ESP8266. This sets me up nicely for Over The Air software upgrades for my home-brew IoT devices. 

Pour éclairer un ciel trop gris,
Pour cueillir un bout de printemps
Il suffit dans la vie
Il suffit bien souvent
De dire adieu tristesse, bonjour sourire.

Happy Trails.

To illuminate a sky too gray,
To pick a piece of spring
It's enough in life
It is often enough
To say goodbye sadness, hello smile.