|
| 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