Skip to content

Commit fc19f77

Browse files
authored
AS3935 Lightning Detector (#407)
* AS3935 Lightning Detector
1 parent 4d39423 commit fc19f77

File tree

2 files changed

+347
-3
lines changed

2 files changed

+347
-3
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,24 @@ Hardware support is provided by specific GPIO, Sensor and Stream modules. It's e
3232
### Sensors
3333

3434
- ADS1x15 analog to digital converters (`ads1x15`)
35+
- ADXl345 3-axis accelerometer up to ±16g (`adxl345`)
3536
- AHT20 temperature and humidity sensor (`aht20`)
37+
- AS3935 lightning detector (`as3935`)
3638
- BH1750 light level sensor (`bh1750`)
3739
- BME280 temperature, humidity and pressure sensor (`bme280`)
3840
- BME680 temperature, humidity and pressure sensor (`bme680`)
3941
- DHT11/DHT22/AM2302 temperature and humidity sensors (`dht22`)
4042
- DS18S20/DS1822/DS18B20/DS1825/DS28EA00/MAX31850K temperature sensors (`ds18b`)
4143
- ENS160 digital multi-gas sensor with multiple IAQ data (TVOC, eCO2, AQI) (`ens160`)
42-
- FREQUENCYCOUNTER Counts pulses from GPIOs and return the frequency in Hz (`frequencycounterr`)
4344
- FLOWSENSOR generic flow rate sensor like YF-S201, YF-DN50 or others (`flowsensor`)
45+
- FREQUENCYCOUNTER Counts pulses from GPIOs and return the frequency in Hz (`frequencycounterr`)
4446
- HCSR04 ultrasonic range sensor (connected to the Raspberry Pi on-board GPIO) (`hcsr04`)
4547
- INA219 DC current sensor (`ina219`)
4648
- LM75 temperature sensor (`lm75`)
4749
- MCP3008 analog to digital converter (`mcp3008`)
48-
- ADXl345 3-axis accelerometer up to ±16g (`adxl345`)
4950
- PMS5003 particulate sensor (`pms5003`)
5051
- SHT40/SHT41/SHT45 temperature and humidity sensors (`sht4x`)
51-
- TLSl2561 light level sensor (`tsl2561`)
52+
- TSL2561 light level sensor (`tsl2561`)
5253
- VEML7700 light level sensor (`veml7700`)
5354
- YF-S201 flow rate sensor (`yfs201`)
5455

mqtt_io/modules/sensor/as3935.py

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
# pylint: disable=line-too-long
2+
# pylint: disable=too-many-branches
3+
# pylint: disable=too-many-statements
4+
"""
5+
6+
AS3935 Ligntning Sensor
7+
8+
Example configuration:
9+
10+
sensor_modules:
11+
- name: AS3935_Sensor
12+
module: as3935
13+
pin: 17
14+
auto_filter: True
15+
indoor: True
16+
17+
sensor_inputs:
18+
- name: distance
19+
module: AS3935_Sensor
20+
digits: 4
21+
interval: 5
22+
type: distance
23+
24+
Module Options
25+
--------------
26+
See also:
27+
https://www.embeddedadventures.com/datasheets/AS3935_Datasheet_EN_v2.pdf
28+
https://github.com/fourstix/Sparkfun_CircuitPython_QwiicAS3935/blob/master/sparkfun_qwiicas3935.py
29+
https://github.com/fourstix/Sparkfun_CircuitPython_QwiicAS3935/blob/master/examples
30+
31+
pin: Interrupt GPIO Pin
32+
auto_filter: Set noise_level, mask_disturber and watchdog_threshold automatically.
33+
Default: True
34+
indoor: Set whether or not the sensor should use an indoor configuration.
35+
Default: True
36+
lightning_threshold: number of lightning detections required before an
37+
interrupt is raised.
38+
Default: 1
39+
watchdog_threshold: This function returns the threshold for events that trigger the IRQ
40+
Pin. Only sensitivity threshold values 1 to 10 allowed.
41+
Default: 2
42+
spike_rejection: This setting, like the watchdog threshold, can help determine
43+
between false events and actual lightning. The shape of the spike is
44+
analyzed during the chip's signal validation routine. Increasing this
45+
value increases robustness at the cost of sensitivity to distant
46+
events.
47+
Default: 2
48+
noise_level: The noise floor level is compared to a known reference voltage. If
49+
this level is exceeded the chip will issue an interrupt to the IRQ pin,
50+
broadcasting that it can not operate properly due to noise (INT_NH).
51+
Check datasheet for specific noise level tolerances when setting this
52+
register.
53+
Default: 2
54+
mask_disturber Setting this True or False will change whether or not disturbers
55+
trigger the IRQ pin.
56+
Default: False
57+
division_ratio: The antenna is designed to resonate at 500kHz and so can be tuned
58+
with the following setting. The accuracy of the antenna must be within
59+
3.5 percent of that value for proper signal validation and distance
60+
estimation. The division ratio can only be set to 16, 32, 64 or 128.
61+
Default: 16
62+
tune_cap: This setting will add capacitance to the series RLC antenna on the
63+
product. It's possible to add 0-120pF in steps of 8pF to the antenna.
64+
The Tuning Cap value will be set between 0 and 120pF, in steps of 8pF.
65+
If necessary, the input value is rounded down to the nearest 8pF.
66+
Default: 0
67+
68+
Sensor Options
69+
--------------
70+
71+
type: The following types are supported:
72+
last: last lightning in unix timestamp format
73+
distance: distance of last lightning in km
74+
energy: energy of last lightning (no unit, no physical meaning)
75+
number: number of lightning events since start
76+
77+
"""
78+
79+
import logging
80+
from typing import Dict
81+
from ...types import CerberusSchemaType, ConfigType, SensorValueType
82+
from . import GenericSensor
83+
84+
_LOG = logging.getLogger(__name__)
85+
86+
REQUIREMENTS = ("gpiozero","sparkfun_qwiicas3935")
87+
88+
CONFIG_SCHEMA: CerberusSchemaType = {
89+
"pin": {
90+
"type": 'integer',
91+
"required": True,
92+
"empty": False
93+
},
94+
"lightning_threshold": {
95+
"type": 'integer',
96+
"required": False,
97+
"empty": False,
98+
"allowed": [1, 5, 9, 16],
99+
"default": 1,
100+
},
101+
"watchdog_threshold": {
102+
"type": 'integer',
103+
"required": False,
104+
"empty": False,
105+
"min": 1,
106+
"max": 10,
107+
"default": 2,
108+
},
109+
"spike_rejection": {
110+
"type": 'integer',
111+
"required": False,
112+
"empty": False,
113+
"min": 1,
114+
"max": 11,
115+
"default": 2,
116+
},
117+
"noise_level": {
118+
"type": 'integer',
119+
"required": False,
120+
"empty": False,
121+
"min": 1,
122+
"max": 7,
123+
"default": 2,
124+
},
125+
"mask_disturber": {
126+
"type": 'boolean',
127+
"required": False,
128+
"empty": False,
129+
"default": False,
130+
},
131+
"auto_filter": {
132+
"type": 'boolean',
133+
"required": False,
134+
"empty": False,
135+
"default": True,
136+
},
137+
"indoor": {
138+
"type": 'boolean',
139+
"required": False,
140+
"empty": False,
141+
"default": True,
142+
},
143+
"division_ratio": {
144+
"type": 'integer',
145+
"required": False,
146+
"empty": False,
147+
"allowed": [16, 32, 64, 128],
148+
"default": 16,
149+
},
150+
"tune_cap": {
151+
"type": 'integer',
152+
"required": False,
153+
"empty": False,
154+
"min": 0,
155+
"max": 120,
156+
"default": 0,
157+
},
158+
}
159+
160+
161+
class FRANKLINSENSOR:
162+
"""
163+
Franklin Sensor class
164+
"""
165+
166+
def __init__(self, gpiozero, lightning, name: str, pin: int) -> None: # type: ignore[no-untyped-def]
167+
self.name = name
168+
self.pin = gpiozero.DigitalInputDevice(pin)
169+
self.pin.when_activated = self.trigger_interrupt
170+
self.lightning = lightning
171+
self.count = 0
172+
self.data = {
173+
"last": int(0),
174+
"distance": float(0),
175+
"energy": float(0),
176+
"number": int(0)
177+
}
178+
179+
def trigger_interrupt(self) -> None:
180+
""" When the interrupt goes high """
181+
# pylint: disable=import-outside-toplevel,attribute-defined-outside-init
182+
# pylint: disable=import-error,no-member
183+
import time # type: ignore
184+
time.sleep(0.05)
185+
_LOG.debug("as3935: Interrupt called!")
186+
interrupt_value = self.lightning.read_interrupt_register()
187+
if interrupt_value == self.lightning.NOISE:
188+
_LOG.debug("as3935: Noise detected.")
189+
if self.lightning.AUTOFILTER is True:
190+
self.reduce_noise()
191+
elif interrupt_value == self.lightning.DISTURBER:
192+
_LOG.debug("as3935: Disturber detected.")
193+
if self.lightning.AUTOFILTER is True:
194+
self.increase_threshold()
195+
elif interrupt_value == self.lightning.LIGHTNING:
196+
_LOG.debug("as3935: Lightning strike detected!")
197+
_LOG.debug("as3935: Approximately: %s km away!", self.lightning.distance_to_storm)
198+
_LOG.debug("as3935: Energy value: %s", self.lightning.lightning_energy)
199+
self.count += 1
200+
now = time.time()
201+
self.data = {
202+
"last": int(now),
203+
"distance": float(self.lightning.distance_to_storm),
204+
"energy": float(self.lightning.lightning_energy),
205+
"number": int(self.count)
206+
}
207+
208+
def reduce_noise(self) -> None:
209+
""" Reduce Noise Level """
210+
value = self.lightning.noise_level
211+
value += 1
212+
if value > 7:
213+
_LOG.debug("as3935: Noise floor is at the maximum value 7.")
214+
return
215+
_LOG.debug("as3935: Increasing the noise event threshold to %s", value)
216+
self.lightning.noise_level = value
217+
218+
def increase_threshold(self) -> None:
219+
""" Increase Watchdog Threshold """
220+
value = self.lightning.watchdog_threshold
221+
value += 1
222+
if value > 10:
223+
self.lightning.mask_disturber = True
224+
_LOG.debug("as3935: Watchdog threshold is at the maximum value 10. Mask disturbers now.")
225+
return
226+
_LOG.debug("as3935: Increasing the disturber watchdog threshold to %s", value)
227+
self.lightning.watchdog_threshold = value
228+
229+
def get_value(self, value: str) -> float:
230+
""" Return the value of 'type' """
231+
ret = float(self.data[value])
232+
return ret
233+
234+
235+
class Sensor(GenericSensor):
236+
"""
237+
Flowsensor: Flow Rate Sensor
238+
"""
239+
240+
SENSOR_SCHEMA: CerberusSchemaType = {
241+
"type": {
242+
"type": 'string',
243+
"required": False,
244+
"empty": False,
245+
"allowed": ['last', 'distance', 'energy', 'number'],
246+
"default": 'distance',
247+
},
248+
}
249+
250+
def setup_module(self) -> None:
251+
# pylint: disable=import-outside-toplevel,attribute-defined-outside-init
252+
# pylint: disable=import-error,no-member
253+
import board # type: ignore
254+
import sparkfun_qwiicas3935 # type: ignore
255+
import gpiozero # type: ignore
256+
257+
# Create gpio object
258+
self.gpiozero = gpiozero
259+
self.sensors: Dict[str, FRANKLINSENSOR] = {}
260+
261+
# Create bus object using our board's I2C port
262+
self.i2c = board.I2C()
263+
264+
# Create as3935 object
265+
self.lightning = sparkfun_qwiicas3935.Sparkfun_QwiicAS3935_I2C(self.i2c)
266+
267+
if self.lightning.connected:
268+
_LOG.debug("as3935: Schmow-ZoW, Lightning Detector Ready!")
269+
else:
270+
_LOG.debug("as3935: Lightning Detector does not appear to be connected. Please check wiring.")
271+
272+
# Set defaults
273+
self.lightning.clear_statistics()
274+
self.lightning.reset()
275+
self.lightning.watchdog_threshold = 2
276+
self.lightning.noise_level = 2
277+
self.lightning.mask_disturber = False
278+
self.lightning.spike_rejection = 2
279+
self.lightning.lightning_threshold = 1
280+
self.lightning.division_ratio = 16
281+
self.lightning.tune_cap = 0
282+
self.lightning.indoor_outdoor = self.lightning.INDOOR
283+
self.lightning.mask_disturber = False
284+
self.lightning.AUTOFILTER = True # Our own var
285+
286+
# Auto Filter False-Positives?
287+
if 'auto_filter' in self.config:
288+
self.lightning.AUTOFILTER=self.config["auto_filter"]
289+
290+
# Lightning Threashold
291+
if 'lightning_threshold' in self.config:
292+
self.lightning.lightning_threshold = self.config["lightning_threshold"]
293+
294+
# Watchdog Threashold
295+
if 'watchdog_threshold' in self.config:
296+
self.lightning.watchdog_threshold = self.config["watchdog_threshold"]
297+
298+
# Spike Rejection
299+
if 'spike_rejection' in self.config:
300+
self.lightning.spike_rejection = self.config["spike_rejection"]
301+
302+
# Noise Floor / Level
303+
if 'noise_level' in self.config:
304+
self.lightning.noise_level = self.config["noise_level"]
305+
306+
# Mask Disturber
307+
if 'mask_disturber' in self.config:
308+
self.lightning.mask_disturber = self.config["mask_disturber"]
309+
310+
# Indoor/Outdoor
311+
if 'indoor' in self.config:
312+
if self.config["indoor"] is True:
313+
self.lightning.indoor_outdoor = self.lightning.INDOOR
314+
else:
315+
self.lightning.indoor_outdoor = self.lightning.OUTDOOR
316+
317+
# Division Ratio
318+
if 'division_ratio' in self.config:
319+
self.lightning.division_ratio = self.config["division_ratio"]
320+
321+
# Tune Cap
322+
if 'tune_cap' in self.config:
323+
self.lightning.tune_cap = self.config["tune_cap"]
324+
325+
# Debug
326+
_LOG.debug("as3935: The noise floor is %s", self.lightning.noise_level)
327+
_LOG.debug("as3935: The disturber watchdog threshold is %s", self.lightning.watchdog_threshold)
328+
_LOG.debug("as3935: The Lightning Detectori's Indoor/Outdoor mode is set to: %s", self.lightning.indoor_outdoor)
329+
_LOG.debug("as3935: Are disturbers being masked? %s", self.lightning.mask_disturber)
330+
_LOG.debug("as3935: Spike Rejection is set to: %s", self.lightning.spike_rejection)
331+
_LOG.debug("as3935: Division Ratio is set to: %s", self.lightning.division_ratio)
332+
_LOG.debug("as3935: Internal Capacitor is set to: %s", self.lightning.tune_cap)
333+
334+
# Create sensor
335+
sensor = FRANKLINSENSOR(
336+
gpiozero=self.gpiozero, lightning=self.lightning, name=self.config["name"], pin=self.config["pin"]
337+
)
338+
self.sensors[sensor.name] = sensor
339+
340+
def get_value(self, sens_conf: ConfigType) -> SensorValueType:
341+
return self.sensors[self.config["name"]].get_value(
342+
sens_conf["type"]
343+
)

0 commit comments

Comments
 (0)