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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy