diff --git a/indi_allsky/config.py b/indi_allsky/config.py index fb34b8563..1d8c9ddbd 100644 --- a/indi_allsky/config.py +++ b/indi_allsky/config.py @@ -770,6 +770,7 @@ class IndiAllSkyConfigBase(object): "F_USER_VAR_SLOT" : "sensor_user_55", "F_I2C_ADDRESS" : "0x52", "F_TITLE_TEMPLATE" : "{name:s} - {label:s} - {probe:s}", + "FC37_ACTIVE_LOW" : True, "OPENWEATHERMAP_APIKEY" : "", "OPENWEATHERMAP_APIKEY_E": "", "WUNDERGROUND_APIKEY" : "", @@ -857,6 +858,9 @@ class IndiAllSkyConfigBase(object): "CUSTOM_SLOT_9" : "sensor_user_18", "CUSTOM_SLOT_9_MIN" : 0.0, }, + "RAIN_SENSOR" : { + "FC37_ACTIVE_LOW" : True, + }, "ADSB" : { "ENABLE" : False, "DUMP1090_URL" : 'https://localhost/dump1090/data/aircraft.json', diff --git a/indi_allsky/constants.py b/indi_allsky/constants.py index 1bc968eb3..6663ee04d 100644 --- a/indi_allsky/constants.py +++ b/indi_allsky/constants.py @@ -193,6 +193,11 @@ SENSOR_USER_CAMERA_SQM_MAG = 8 SENSOR_USER_CAMERA_SQM_ADU = 9 +RAIN_MAP_STR = { + 0 : 'No Rain', + 1 : 'Raining', +} + SENSOR_INDEX_MAP = { 'sensor_user_0' : 0, diff --git a/indi_allsky/devices/sensors/__init__.py b/indi_allsky/devices/sensors/__init__.py index f90f119a9..01ffeef25 100644 --- a/indi_allsky/devices/sensors/__init__.py +++ b/indi_allsky/devices/sensors/__init__.py @@ -79,6 +79,7 @@ from .lightningSensorAs3935 import LightningSensorAs3935_SparkFun_SPI as blinka_sparkfun_lightning_sensor_as3935_spi from .lightningSensorAs3935 import LightningSensorAs3935_SparkFun_I2C as blinka_sparkfun_lightning_sensor_as3935_i2c +from .rainSensorFc37 import RainSensorFc37 as blinka_rain_sensor_fc37 from .mqttBrokerSensor import MqttBrokerSensor as mqtt_broker_sensor @@ -133,6 +134,7 @@ 'kernel_temp_sensor_ds18x20_w1', 'blinka_sparkfun_lightning_sensor_as3935_spi', 'blinka_sparkfun_lightning_sensor_as3935_i2c', + 'blinka_rain_sensor_fc37', 'mqtt_broker_sensor', 'temp_api_openweathermap', 'temp_api_weatherunderground', diff --git a/indi_allsky/devices/sensors/rainSensorFc37.py b/indi_allsky/devices/sensors/rainSensorFc37.py new file mode 100644 index 000000000..e002704aa --- /dev/null +++ b/indi_allsky/devices/sensors/rainSensorFc37.py @@ -0,0 +1,70 @@ +import logging + +from .sensorBase import SensorBase +from ... import constants +from ..exceptions import SensorException + +logger = logging.getLogger('indi_allsky') + + +class RainSensorFc37(SensorBase): + + METADATA = { + 'name': 'FC-37 Rain Sensor', + 'description': 'FC-37 Rain Detection Sensor (digital output)', + 'count': 1, + 'labels': ( + 'Rain Detected', + ), + 'types': ( + constants.SENSOR_PRECIPITATION, + ), + } + + def __init__(self, *args, **kwargs): + super(RainSensorFc37, self).__init__(*args, **kwargs) + + pin_1_name = kwargs.get('pin_1_name') + if not pin_1_name: + raise SensorException('FC-37 sensor pin not configured (RAIN_SENSOR.__*_PIN_1 or TEMP_SENSOR.__*_PIN_1)') + + try: + import board + import digitalio + except Exception as e: + raise SensorException('FC-37 sensor requires board/digitalio support: %s' % str(e)) from e + + if not hasattr(board, pin_1_name): + raise SensorException('FC-37 sensor pin name "%s" is not valid' % pin_1_name) + + self.sensor_pin = digitalio.DigitalInOut(getattr(board, pin_1_name)) + self.sensor_pin.direction = digitalio.Direction.INPUT + self.sensor_pin.pull = digitalio.Pull.UP + + self.active_low = bool( + self.config.get('RAIN_SENSOR', {}).get('FC37_ACTIVE_LOW', True) + ) + + logger.warning('[%s] Initialized FC-37 rain sensor on pin %s, active_low=%s', self.name, pin_1_name, self.active_low) + + def update(self): + try: + raw_value = self.sensor_pin.value + except Exception as e: + raise SensorException('FC-37 sensor read failure: %s' % str(e)) from e + + # FC-37 TFT digital output is typically low when water is detected. + detected = (not raw_value) if self.active_low else raw_value + + rain_value = 1.0 if detected else 0.0 + rain_state = constants.RAIN_MAP_STR.get(int(rain_value), 'Unknown') + + logger.info('[%s] FC-37 rain sensor: %s (%s)', self.name, rain_state, rain_value) + + return {'data': (rain_value,), 'state': rain_state} + + def deinit(self): + try: + self.sensor_pin.deinit() + except Exception: + pass diff --git a/indi_allsky/flask/forms.py b/indi_allsky/flask/forms.py index c4df76e52..1bd735804 100644 --- a/indi_allsky/flask/forms.py +++ b/indi_allsky/flask/forms.py @@ -857,6 +857,7 @@ def IMAGE_LABEL_TEMPLATE_validator(form, field): 'dew_heater_status' : '', 'fan_status' : '', 'wind_dir' : '', + 'rain_sensor' : 'No Rain', 'custom_1' : '', 'custom_2' : '', 'custom_3' : '', @@ -3990,6 +3991,9 @@ class IndiAllskyConfigForm(FlaskForm): ('blinka_sparkfun_lightning_sensor_as3935_spi', 'AS3935 SPI - (6 slots) [BETA]'), ('blinka_sparkfun_lightning_sensor_as3935_i2c', 'AS3935 i2c - (6 slots) [BETA]'), ), + 'Rain Sensors' : ( + ('blinka_rain_sensor_fc37', 'FC-37 Rain Sensor - digital (1 slot)'), + ), 'Remote' : ( ('mqtt_broker_sensor', 'MQTT Broker Sensor - (10 slots)'), ), @@ -4937,6 +4941,7 @@ class IndiAllskyConfigForm(FlaskForm): TEMP_SENSOR__F_USER_VAR_SLOT = SelectField('Sensor F Initial Slot', choices=SENSOR_USER_VAR_SLOT_choices, validators=[SENSOR_USER_VAR_SLOT_validator]) TEMP_SENSOR__F_I2C_ADDRESS = StringField('I2C Address', validators=[DataRequired(), I2C_ADDRESS_validator]) TEMP_SENSOR__F_TITLE_TEMPLATE = StringField('Chart Title Template', validators=[DataRequired(), TEMP_SENSOR__TITLE_TEMPLATE_validator]) + RAIN_SENSOR__FC37_ACTIVE_LOW = BooleanField('Rain Sensor FC-37 -Invert logic') TEMP_SENSOR__OPENWEATHERMAP_APIKEY = PasswordField('OpenWeatherMap API Key', widget=PasswordInput(hide_value=False), validators=[TEMP_SENSOR__OPENWEATHERMAP_APIKEY_validator], render_kw={'autocomplete' : 'new-password'}) TEMP_SENSOR__WUNDERGROUND_APIKEY = PasswordField('Weather Underground API Key', widget=PasswordInput(hide_value=False), validators=[TEMP_SENSOR__WUNDERGROUND_APIKEY_validator], render_kw={'autocomplete' : 'new-password'}) TEMP_SENSOR__ASTROSPHERIC_APIKEY = PasswordField('Astrospheric API Key', widget=PasswordInput(hide_value=False), validators=[TEMP_SENSOR__ASTROSPHERIC_APIKEY_validator], render_kw={'autocomplete' : 'new-password'}) @@ -5279,6 +5284,7 @@ def validate(self): result = False + if self.IMAGE_CROP_ROI_X1.data and self.IMAGE_CROP_ROI_Y1.data and self.IMAGE_CROP_ROI_X2.data and self.IMAGE_CROP_ROI_Y2.data: if self.IMAGE_CROP_ROI_X2.data <= self.IMAGE_CROP_ROI_X1.data: self.IMAGE_CROP_ROI_X2.errors.append('X2 must be greater than X1') @@ -6053,7 +6059,6 @@ def validate(self): self.TEMP_SENSOR__A_PIN_1.errors.append('Topics must be defined') result = False - # sensor B if self.TEMP_SENSOR__B_CLASSNAME.data: if self.TEMP_SENSOR__B_CLASSNAME.data.startswith('blinka_'): @@ -6606,6 +6611,7 @@ def validate(self): }) + for slot1, slot2 in itertools.combinations(check_sensor_slots, 2): if not slot1['set'].isdisjoint(slot2['set']): slot1['slot'].errors.append('Overlapping slots with {0:s}'.format(slot2['name'])) diff --git a/indi_allsky/flask/templates/config.html b/indi_allsky/flask/templates/config.html index 435056242..751600fac 100644 --- a/indi_allsky/flask/templates/config.html +++ b/indi_allsky/flask/templates/config.html @@ -2649,7 +2649,7 @@
timestamp, ts, day_date, exposure, rational_exp, gain_f, temp, temp_unit,
sidereal_time, sqm, stars, detections, stack_method, stack_count, stretch
location, owner, latitude, longitude, kpindex, ovation_max,
sun_alt, moon_alt, moon_phase, sun_moon_sep, moon_up, sun_moon_sep,
mercury_alt, mercury_up, venus_alt, venus_up, venus_phase,
mars_alt, mars_up, jupiter_alt, jupiter_up, saturn_alt, saturn_up,
iss_alt, iss_up, iss_next_h, iss_next_alt, hst_alt, hst_up, hst_next_h,
hst_next_alt, tiangong_alt, tiangong_up, tiangong_next_h, tiangong_next_alt
timestamp, ts, day_date, exposure, rational_exp, gain_f, temp, temp_unit,
sidereal_time, sqm, stars, detections, stack_method, stack_count, stretch
location, owner, latitude, longitude, kpindex, ovation_max,
sun_alt, moon_alt, moon_phase, sun_moon_sep, moon_up, sun_moon_sep,
mercury_alt, mercury_up, venus_alt, venus_up, venus_phase,
mars_alt, mars_up, jupiter_alt, jupiter_up, saturn_alt, saturn_up,
iss_alt, iss_up, iss_next_h, iss_next_alt, hst_alt, hst_up, hst_next_h,
hst_next_alt, tiangong_alt, tiangong_up, tiangong_next_h, tiangong_next_alt,
rain_sensor