Lightning Detector, translating from Arduino?

Good morning!
I want to move my AS3935 lightning detector to an ESP32, so I can ESPNow it to my base station. I found a script for ESP8266 that addresses the calibration and noise problems I’ve had- and it uses a NeoPixel, but now that I apparently have the libraries installed it’s throwing up a ton of errors that indicate I’ve been Arduinoed again.

(these are the three odd libraries:

GitHub - esp8266/Arduino: ESP8266 core for Arduino)

This is the text of the program. I would really like it if we could declare everything properly enough to work in a real IDE, and I reallyreally want it to be on an ESP32. So even if I were to successfully pick through and make the Arduino code work, I still don’t have the skills to move it to the other platform.

/*
 * Kaminari
 *
 * Copyright (C) 2020 Richard "Shred" Körber
 *   https://github.com/shred/kaminari
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */

#include <Arduino.h>
#include <SPI.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Adafruit_NeoPixel.h>
#include <PubSubClient.h>
#include "AS3935.h"
#include "Config.h"
#include "myWiFi.h"

#define SPI_CS 15   // IO15 (D8)
#define SPI_MOSI 13 // IO13 (D7)
#define SPI_MISO 12 // IO12 (D6)
#define SPI_SCK 14  // IO14 (D5)
#define AS_INT 5    // IO5  (D1)
#define NEOPIXEL 4  // IO4  (D2)

#define PORT 80

#define COLOR_CONNECTING 0x00800000
#define COLOR_CALIBRATING 0x00404000
#define COLOR_BLACK 0x00000000

#define DISTANCE_ANIMATION_TIME 5000
#define ENERGY_ANIMATION_TIME 500
#define DISTURBER_ANIMATION_TIME 1000

ConfigManager cfgMgr;
ESP8266WebServer server(PORT);
AS3935 detector(SPI_CS, AS_INT);
Adafruit_NeoPixel neopixel(1, NEOPIXEL, NEO_GRBW + NEO_KHZ800);
WiFiClient wifiClient;
PubSubClient client(MY_MQTT_SERVER_HOST, MY_MQTT_SERVER_PORT, wifiClient);
WiFiEventHandler connectedEventHandler, gotIpEventHandler, disconnectedEventHandler;

unsigned long beforeAnimation = millis();
unsigned long beforeMqttConnection = millis();
bool connected = false;
unsigned int currentColor = 0;
Lightning ledLightning;

inline static unsigned long timeDifference(unsigned long now, unsigned long past)
{
  // This is actually safe from millis() overflow because all types are unsigned long!
  return past > 0 ? (now - past) : 0;
}

void sendJsonResponse(ArduinoJson::JsonDocument &doc, int status = 200)
{
  String json;
  serializeJson(doc, json);
  server.send(status, "application/json", json);
}

bool authenticated()
{
  if (server.header("X-API-Key") == MY_APIKEY)
  {
    return true;
  }
  if (server.arg("api_key") == MY_APIKEY)
  {
    return true;
  }
  server.send(403);
  return false;
}

void handleStatus()
{
  DynamicJsonDocument doc(8192);

  unsigned long now = millis();

  JsonArray la = doc.createNestedArray("lightnings");
  Lightning lightning;
  for (int ix = 0;; ix++)
  {
    if (!detector.getLastLightningDetection(ix, lightning))
    {
      break;
    }
    StaticJsonDocument<200> subdoc;
    subdoc["age"] = timeDifference(now, lightning.time) / 1000;
    subdoc["energy"] = lightning.energy;
    if (lightning.distance < 0x3F)
    {
      subdoc["distance"] = lightning.distance;
    }
    else
    {
      subdoc["distance"] = (char *)NULL;
    }
    la.add(subdoc);
  }

  unsigned int distance = detector.getEstimatedDistance();
  if (distance < 0x3F)
  {
    doc["distance"] = distance;
  }
  else
  {
    doc["distance"] = (char *)NULL;
  }

  doc["energy"] = detector.getEnergy();
  doc["noiseFloorLevel"] = detector.getNoiseFloorLevel();
  doc["disturbersPerMinute"] = detector.getDisturbersPerMinute();
  doc["watchdogThreshold"] = detector.getWatchdogThreshold();
  doc["wifiSignalStrength"] = WiFi.RSSI();

  sendJsonResponse(doc);
}

void handleSettings()
{
  DynamicJsonDocument doc(1024);
  doc["tuning"] = detector.getFrequency();
  doc["noiseFloorLevel"] = detector.getNoiseFloorLevel();
  doc["outdoorMode"] = detector.getOutdoorMode();
  doc["watchdogThreshold"] = detector.getWatchdogThreshold();
  doc["minimumNumberOfLightning"] = detector.getMinimumNumberOfLightning();
  doc["spikeRejection"] = detector.getSpikeRejection();
  doc["outdoorMode"] = detector.getOutdoorMode();
  doc["statusLed"] = cfgMgr.config.ledEnabled;
  doc["blueBrightness"] = cfgMgr.config.blueBrightness;
  doc["disturberBrightness"] = cfgMgr.config.disturberBrightness;
  sendJsonResponse(doc);
}

void handleUpdate()
{
  if (authenticated())
  {
    bool needsClearing = false;

    if (server.hasArg("watchdogThreshold"))
    {
      long val = String(server.arg("watchdogThreshold")).toInt();
      if (val >= 0 && val <= 10)
      {
        cfgMgr.config.watchdogThreshold = val;
        detector.setWatchdogThreshold(val);
        needsClearing = true;
      }
    }

    if (server.hasArg("minimumNumberOfLightning"))
    {
      long val = String(server.arg("minimumNumberOfLightning")).toInt();
      if (val == 1 || val == 5 || val == 9 || val == 16)
      {
        cfgMgr.config.minimumNumberOfLightning = val;
        detector.setMinimumNumberOfLightning(val);
      }
    }

    if (server.hasArg("spikeRejection"))
    {
      long val = String(server.arg("spikeRejection")).toInt();
      if (val >= 0 && val <= 11)
      {
        cfgMgr.config.spikeRejection = val;
        detector.setSpikeRejection(val);
        needsClearing = true;
      }
    }

    if (server.hasArg("outdoorMode"))
    {
      bool val = String(server.arg("outdoorMode")) == "true";
      cfgMgr.config.outdoorMode = val;
      detector.setOutdoorMode(val);
      needsClearing = true;
    }

    if (server.hasArg("statusLed"))
    {
      cfgMgr.config.ledEnabled = String(server.arg("statusLed")) == "true";
    }

    if (server.hasArg("blueBrightness"))
    {
      long val = String(server.arg("blueBrightness")).toInt();
      if (val >= 0 && val <= 255)
      {
        cfgMgr.config.blueBrightness = val;
      }
    }

    if (server.hasArg("disturberBrightness"))
    {
      long val = String(server.arg("disturberBrightness")).toInt();
      if (val >= 0 && val <= 255)
      {
        cfgMgr.config.disturberBrightness = val;
      }
    }

    cfgMgr.commit();
    handleSettings();

    if (needsClearing)
    {
      // clear detector statistics after changing the detector config
      detector.clearStatistics();
    }
  }
}

void handleCalibrate()
{
  if (authenticated())
  {
    unsigned int oldColor = currentColor;
    color(COLOR_CALIBRATING);
    StaticJsonDocument<200> doc;
    doc["tuning"] = detector.calibrate();
    sendJsonResponse(doc);
    color(oldColor);
  }
}

void handleClear()
{
  if (authenticated())
  {
    detector.clearDetections();
    server.send(200, "text/plain", "OK");
  }
}

void handleReset()
{
  if (authenticated())
  {
    detector.reset();
    handleCalibrate();
    setupDetector();
    detector.clearStatistics();
  }
}

void sendMqttStatus()
{
  DynamicJsonDocument doc(8192);
  String json;

  Lightning lightning;
  if (detector.getLastLightningDetection(0, lightning))
  {
    doc["energy"] = lightning.energy;
    if (lightning.distance < 0x3F)
    {
      doc["distance"] = lightning.distance;
    }
    else
    {
      doc["distance"] = (char *)NULL;
    }
  }
  else
  {
    doc["energy"] = (char *)NULL;
    doc["distance"] = (char *)NULL;
  }

  doc["tuning"] = detector.getFrequency();
  doc["noiseFloorLevel"] = detector.getNoiseFloorLevel();
  doc["disturbersPerMinute"] = detector.getDisturbersPerMinute();
  doc["watchdogThreshold"] = detector.getWatchdogThreshold();
  doc["wifiSignalStrength"] = WiFi.RSSI();

  serializeJson(doc, json);
  if (!client.publish(MY_MQTT_TOPIC, json.c_str(), MY_MQTT_RETAIN))
  {
    Serial.print("Failed to send MQTT message, rc=");
    Serial.println(client.state());
  }
}

void setupDetector()
{
  detector.setOutdoorMode(cfgMgr.config.outdoorMode);
  detector.setWatchdogThreshold(cfgMgr.config.watchdogThreshold);
  detector.setMinimumNumberOfLightning(cfgMgr.config.minimumNumberOfLightning);
  detector.setSpikeRejection(cfgMgr.config.spikeRejection);
}

void color(unsigned int color)
{
  if (color != currentColor)
  {
    neopixel.fill(color);
    neopixel.show();
    currentColor = color;
  }
}

inline int limit(int v)
{
  return v <= 255 ? (v >= 0 ? v : 0) : 255;
}

void updateColor(unsigned long now)
{
  if (!connected)
  {
    color(now % 1000 <= 500 ? COLOR_CONNECTING : COLOR_BLACK);
    return;
  }

  if (!cfgMgr.config.ledEnabled)
  {
    color(COLOR_BLACK);
    return;
  }

  // Calculate blue color (noise floor level)
  int blue = 0;
  if (detector.isNoiseFloorLevelOutOfRange())
  {
    blue = now % 1000 <= 500 ? (cfgMgr.config.blueBrightness > 128 ? cfgMgr.config.blueBrightness : 128) : 0;
  }
  else
  {
    blue = detector.getNoiseFloorLevel() * cfgMgr.config.blueBrightness / (detector.getOutdoorMode() ? 2000 : 146);
  }

  // Calculate lightning age (white) and distance (green to red)
  long red = 0;
  long green = 0;
  long white = 0;
  if (ledLightning.time > 0 && ledLightning.distance <= 40)
  {
    unsigned long diff = timeDifference(now, ledLightning.time);
    long distancePast = (DISTANCE_ANIMATION_TIME + ENERGY_ANIMATION_TIME) - diff;
    long energyPast = ENERGY_ANIMATION_TIME - diff;
    if (energyPast >= 0)
    {
      white = ((ledLightning.energy) * energyPast * 255) / (300000 * ENERGY_ANIMATION_TIME);
      blue = 0;
    }
    else if (distancePast >= 0)
    {
      green = ((ledLightning.distance) * distancePast * 255) / (40 * DISTANCE_ANIMATION_TIME);
      red = ((40 - ledLightning.distance) * distancePast * 255) / (40 * DISTANCE_ANIMATION_TIME);
      long newblue = ((long)blue) * diff / (DISTANCE_ANIMATION_TIME + ENERGY_ANIMATION_TIME);
      if (newblue < blue)
      {
        blue = newblue;
      }
    }
  }

  // Calculate last disturber fade
  unsigned long lastDisturber = detector.getLastDisturber();
  unsigned long disturberDiff = timeDifference(now, lastDisturber);
  if (cfgMgr.config.disturberBrightness > 0 && lastDisturber != 0 && disturberDiff <= DISTURBER_ANIMATION_TIME)
  {
    red = (cfgMgr.config.disturberBrightness * (DISTURBER_ANIMATION_TIME - disturberDiff)) / DISTURBER_ANIMATION_TIME;
  }

  color((limit(white) & 0xFF) << 24 | (limit(red) & 0xFF) << 16 | (limit(green) & 0xFF) << 8 | (limit(blue) & 0xFF));
}

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

  cfgMgr.begin();

  detector.begin();

  neopixel.begin();
  neopixel.setBrightness(255);

  // Init detector
  color(COLOR_CALIBRATING);
  Serial.println("Calibrating antenna...");
  detector.reset();
  unsigned int freq = detector.calibrate();
  Serial.print("Calibrated antenna frequency: ");
  Serial.print(freq);
  Serial.println(" Hz");
  color(COLOR_BLACK);

  // Set up the detector
  setupDetector();

  // Start WiFi
  WiFi.setSleepMode(WIFI_NONE_SLEEP);
  WiFi.mode(WIFI_STA);
  connectedEventHandler = WiFi.onStationModeConnected([](const WiFiEventStationModeConnected &event)
                                                      {
        Serial.print("WiFi connected to: ");
        Serial.println(WiFi.SSID());
        Serial.print("MAC: ");
        Serial.println(WiFi.macAddress()); });
  gotIpEventHandler = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &event)
                                              {
        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());
        server.begin();
        Serial.print("Server is listening on port: ");
        Serial.println(PORT);
        connected = true; });
  disconnectedEventHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected &event)
                                                            {
        connected = false;
        Serial.println("WiFi connection lost. Reconnecting...");
        WiFi.begin(MY_SSID, MY_PSK); });
  WiFi.begin(MY_SSID, MY_PSK);

  // Start MDNS
  if (MDNS.begin(MY_MDNS_NAME))
  {
    Serial.println("MDNS responder started");
    MDNS.addService("http", "tcp", PORT);
  }

  // Start server
  server.on("/status", handleStatus);
  server.on("/settings", handleSettings);
  server.on("/update", handleUpdate);
  server.on("/calibrate", handleCalibrate);
  server.on("/clear", handleClear);
  server.on("/reset", handleReset);
  server.onNotFound([]()
                    { server.send(404, "text/plain", server.uri() + ": not found\n"); });
  const char *headerkeys[] = {"X-API-Key"};
  server.collectHeaders(headerkeys, sizeof(headerkeys) / sizeof(char *));
}

void loop()
{
  const unsigned long now = millis();

  if (connected)
  {
    server.handleClient();
    MDNS.update();

#ifdef MY_MQTT_ENABLED
    if (!client.loop() && timeDifference(now, beforeMqttConnection) > 1000)
    {
      beforeMqttConnection = now;
      if (client.connect(MY_MQTT_CLIENT_ID, MY_MQTT_USER, MY_MQTT_PASSWORD))
      {
        Serial.println("Successfully connected to MQTT server");
      }
      else
      {
        Serial.print("Connection to MQTT server failed, rc=");
        Serial.println(client.state());
      }
    }
#endif
  }

  if (detector.update())
  {
    if (!detector.getLastLightningDetection(0, ledLightning))
    {
      ledLightning.time = 0;
    }
#ifdef MY_MQTT_ENABLED
    if (connected && client.connected())
    {
      sendMqttStatus();
    }
#endif
  }

  if (timeDifference(now, beforeAnimation) > 50)
  {
    beforeAnimation = now;
    updateColor(now);
  }
}

Please also post your platformio.ini file, in case thee’s something in there that’s halping to cause the problems.

Also, ESP32 is not the same board as ESP8266, so I suspect that’s the source of your errors. What actual board are you using for your ESP32?

Cheers,
Norm.

Hi Norm, I know it’s not written for ESP32. I wish it were. I have an 8266 at the ready, but I haven’t figured out how to use ESPNow with the 8266.

I don’t know which of the libdeps are unnecessary, but I’m pretty sure some are.

platformio.ini:

[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps =
    SPI
    bblanchon/ArduinoJson@^6.21.3
    ESP8266WiFi
    https://github.com/esp8266/Arduino.git
    https://github.com/xoseperez/eeprom_rotate
    WiFiClient
    ESP8266WebServer
    ESP8266mDNS
    adafruit/Adafruit NeoPixel@^1.11.0
    https://github.com/knolleary/pubsubclient
    stevemarple/AS3935@^1.0.5

Hi Joe,

I’ve created a project with your source and platformio.ini files and I had to fix the ini file asto the folloing as the “github” stuff you posted above is invalid:

[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps =
    SPI
    bblanchon/ArduinoJson@^6.21.3
    ESP8266WiFi
    https://github.com/esp8266/Arduino
    https://github.com/xoseperez/eeprom_rotate
    WiFiClient
    ESP8266WebServer
    ESP8266mDNS
    adafruit/Adafruit NeoPixel@^1.11.0
    https://github.com/knolleary/pubsubclient
    stevemarple/AS3935@^1.0.5

Everything seems ok with that, apart from the WiFiClient library which is not found, and a search of the registry doesn’t come back with anything that appears useful. :cry:

However, a compilation gives lots of errors, as you noted in your first post, similar to the following:

src/main.cpp:141:33: error: 'class AS3935' has no member named 'getOutdoorMode'
  141 |   doc["outdoorMode"] = detector.getOutdoorMode();
      |                                 ^~~~~~~~~~~~~~
src/main.cpp:142:39: error: 'class AS3935' has no member named 'getWatchdogThreshold'
  142 |   doc["watchdogThreshold"] = detector.getWatchdogThreshold();
      |                                       ^~~~~~~~~~~~~~~~~~~~
src/main.cpp:143:46: error: 'class AS3935' has no member named 'getMinimumNumberOfLightning'
  143 |   doc["minimumNumberOfLightning"] = detector.getMinimumNumberOfLightning();

Having looked at the source for the AS3935 library as listed, these are indeed valid errors. Those functions being called are not correct as they do not exist in the 1.0.5 version of the library. Are you sure this is the correct version? I checked on GitHub too, 1.0.5 is the latest version.

I’m thinking that the source you are using is not meant to be used with the library you have installed. So I went to another github, the one in the comments at the start of your main code above, https://github.com/shred/kaminari/tree/master/kaminari, and found two files, AS3935.h and AS3935.cpp which do have the missing functions.

After adding those two files to my project, and removing the as3935 library from platformio.ini, I got far fewer compilation errors, but still there were some related to the AS3935 device.

I also got errors relating to WiFi passwords and SSIDs, as well as a missing config.h file, but there are still AS3935 errors. So, it would appear that the original code that you have is not compatible with it’s own sources!!!

Sorry, I can’t be of more help. :frowning_face:.

Cheers,
Norm.

Well, thank for letting me know I’m at a dead end.
I had it running on the Mega side of my RobotDyn 8266 Mega, but I hate that piece of crap, so I dismantled it.
I had the basic program running on the Uno, but there was too much noise and no way to adjust gain or anything.
I reallyreally want to make an ESPNow sender that would interrupt the sensor data display with LIGHTNING! and the distance and intensity. Then I was going to datalog it, along with pressure, etc.
I might be able to write my own program. Didn;'t think of that…