class WaterSensor(ComponentSensor): DEBUG = False def __init__(self, adc, power_pin=None, cutoff_voltage=None, interval_publish=None, interval_reading=1, mqtt_topic=None, friendly_name=None, discover=True, expose_intervals=False, intervals_topic=None): interval_publish = interval_publish or -1 global _unit_index _unit_index += 1 super().__init__(COMPONENT_NAME, __version__, _unit_index, discover, interval_publish, interval_reading, mqtt_topic, _log, expose_intervals, intervals_topic) self._adc = ADC(adc) self._ppin = Pin(power_pin, machine.Pin.OUT) if power_pin is not None else None self._cv = cutoff_voltage or self._adc.maxVoltage() self._lv = None self._addSensorType(SENSOR_BINARY_MOISTURE, 0, 0, VALUE_TEMPLATE, "", friendly_name, mqtt_topic, None, True) self._pub_coro = None async def _read(self): a = time.ticks_us() p = self._ppin if p is not None: p.value(1) vol = self._adc.readVoltage() if self.DEBUG is True: print("#{!s}, V".format(self.getTopic(SENSOR_BINARY_MOISTURE)[-1]), vol) if p is not None: p.value(0) if vol >= self._cv: state = False if self._lv != state: # dry if self._pub_coro is not None: asyncio.cancel(self._pub_coro) self._pub_coro = _mqtt.publish(self.getTopic(SENSOR_BINARY_MOISTURE), "OFF", qos=1, retain=True, timeout=None, await_connection=True) asyncio.get_event_loop().create_task(self._pub_coro) self._lv = state else: state = True if self._lv != state: # wet if self._pub_coro is not None: asyncio.cancel(self._pub_coro) self._pub_coro = _mqtt.publish(self.getTopic(SENSOR_BINARY_MOISTURE), "ON", qos=1, retain=True, timeout=None, await_connection=True) asyncio.get_event_loop().create_task(self._pub_coro) self._lv = state b = time.ticks_us() if WaterSensor.DEBUG: print("Water measurement took", (b - a) / 1000, "ms")
class EC(Component): DEBUG = False def __init__(self, r1, ra, adc, power_pin, ppm_conversion, temp_coef, k, temp_sensor, precision_ec=3, interval=None, topic_ec=None, topic_ppm=None, friendly_name_ec=None, friendly_name_ppm=None): super().__init__() self._interval = interval or config.INTERVAL_SEND_SENSOR self._prec_ec = int(precision_ec) self._adc = ADC(adc) self._ppin = Pin(power_pin, machine.Pin.OUT) self._r1 = r1 self._ra = ra self._ppm_conversion = ppm_conversion self._temp_coef = temp_coef self._k = k self._temp = temp_sensor if hasattr(temp_sensor, "temperature") is False: raise AttributeError( "Temperature sensor {!s}, type {!s} has no async method temperature()".format(temp_sensor, type(temp_sensor))) gc.collect() self._ec25 = None self._ppm = None self._time = 0 # This makes it possible to use multiple instances of MySensor global _count self._count = _count _count += 1 self._topic_ec = topic_ec or _mqtt.getDeviceTopic("{!s}/{!s}".format("EC", self._count)) self._topic_ppm = topic_ppm or _mqtt.getDeviceTopic("{!s}/{!s}".format("PPM", self._count)) self._frn_ec = friendly_name_ec self._frn_ppm = friendly_name_ppm async def _init(self): await super()._init() gen = self._read interval = self._interval while True: await gen() await asyncio.sleep(interval) async def _discovery(self): sens = '"unit_of_meas":"mS",' \ '"val_tpl":"{{ value|float }}",' name = "{!s}{!s}{!s}".format(_component_name, self._count, "EC25") await self._publishDiscovery(_component_type, self._topic_ec, name, sens, self._frn_ec or "EC25") del sens, name gc.collect() sens = '"unit_of_meas":"ppm",' \ '"val_tpl":"{{ value|int }}",' name = "{!s}{!s}{!s}".format(_component_name, self._count, "PPM") await self._publishDiscovery(_component_type, self._topic_ppm, name, sens, self._frn_ppm or "PPM") del sens, name gc.collect() async def _read(self, publish=True): if time.ticks_ms() - self._time < 5000: self._time = time.ticks_ms() await asyncio.sleep(5) temp = await self._temp.temperature(publish=False) if temp is None: await asyncio.sleep(3) temp = await self._temp.temperature(publish=False) if temp is None: _log.warn("Couldn't get temperature, aborting EC measurement") self._ec25 = None self._ppm = None return None, None self._ppin.value(1) vol = self._adc.readVoltage() # vol = self._adc.readVoltage() # micropython on esp is probably too slow to need this. It was intended for arduino if self.DEBUG is True: print("Temp", temp) print("V", vol) self._ppin.value(0) if vol >= self._adc.maxVoltage(): ec25 = 0 ppm = 0 await _log.asyncLog("warn", "Cable not in fluid") else: if vol <= 0.5: _log.warn("Voltage <=0.5, change resistor") rc = (vol * (self._r1 + self._ra)) / (self._adc.maxVoltage() - vol) # rc = rc - self._ra # sensor connected to ground, not a pin, therefore no Ra ec = 1000 / (rc * self._k) ec25 = ec / (1 + self._temp_coef * (temp - 25.0)) ppm = int(ec25 * self._ppm_conversion * 1000) ec25 = round(ec25, self._prec_ec) if self.DEBUG: print("Rc", rc) print("EC", ec) print("EC25", ec25, "MilliSimens") print("PPM", ppm) self._ec25 = ec25 self._ppm = ppm self._time = time.ticks_ms() if publish: await _mqtt.publish(self._topic_ec, ("{0:." + str(self._prec_ec) + "f}").format(ec25)) await _mqtt.publish(self._topic_ppm, ppm) return ec25, ppm async def ec(self, publish=True): if time.ticks_ms() - self._time > 5000: await self._read(publish) return self._ec25 async def ppm(self, publish=True): if time.ticks_ms() - self._time > 5000: await self._read(publish) return self._ppm
class EC(ComponentSensor): DEBUG = True def __init__(self, r1, ra, rg, adc, power_pin, ground_pin, ppm_conversion, temp_coef, k, temp_sensor: ComponentSensor, read_timeout=400, iterations=1, precision_ec=3, friendly_name_ec=None, friendly_name_ppm=None, **kwargs): # This makes it possible to use multiple instances of MySensor global _unit_index _unit_index += 1 self.checkSensorType(temp_sensor, SENSOR_TEMPERATURE) super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, **kwargs) self._temp = temp_sensor self._addSensorType("ec", precision_ec, 0, VALUE_TEMPLATE_JSON.format("ec|float"), "mS", friendly_name_ec or "EC", self._topic, DISCOVERY_EC) self._addSensorType("ppm", 0, 0, VALUE_TEMPLATE_JSON.format("ppm|int"), "ppm", friendly_name_ppm or "PPM", self._topic, DISCOVERY_PPM) self._adc = ADC(adc) self._ppin = Pin(power_pin, machine.Pin.IN) # changing to OUTPUT GND when needed self._gpin = Pin(ground_pin, machine.Pin.IN) # changing to OUTPUT GND when needed self._r1 = r1 self._ra = ra self._rg = rg self._ppm_conversion = ppm_conversion self._temp_coef = temp_coef self._k = k self._to = read_timeout self._iters = iterations gc.collect() @micropython.native @staticmethod def _read_sync(_gpin_init, _ppin_init, _ppin_value, _adc_read, _in, ticks, ticks_diff): a = ticks() _ppin_value(1) vol = _adc_read() b = ticks() # vol = _adc_read() # micropython on esp is way too slow to need this, it was for arduino. # _ppin_value(0) # if not using ppin as INPUT while not reading _gpin_init(_in) _ppin_init(_in) return vol, ticks_diff(b, a) async def _read(self): temp = await self._temp.getValue(SENSOR_TEMPERATURE) if temp is None: await asyncio.sleep(30) temp = await self._temp.getValue(SENSOR_TEMPERATURE) if temp is None: _log.warn("Couldn't get temperature, aborting EC measurement") return vols = [] diffs = [] adcs = [] for _ in range(self._iters): self._gpin.init(machine.Pin.OUT, value=0) self._ppin.init(machine.Pin.OUT, value=0) adc, diff = self._read_sync(self._gpin.init, self._ppin.init, self._ppin.value, self._adc.read, machine.Pin.IN, time.ticks_us, time.ticks_diff) vol = self._adc.convertToVoltage(adc) vols.append(vol) adcs.append(adc) diffs.append(diff) if self._iters > 1: await asyncio.sleep(20) r = [] for i, diff in enumerate(diffs): if diff > self._to: r.append(i) elif vols[i] >= self._adc.maxVoltage(): r.append(i) if len(r) == len(diffs): vol = vols[0] adc = adcs[0] diff = diffs[0] else: for i in range(len(r) - 1, -1, -1): diffs.pop(r[i]) adcs.pop(r[i]) vols.pop(r[i]) vol = sum(vols) / len(vols) adc = sum(adcs) / len(adcs) diff = sum(diffs) / len(diffs) if self.DEBUG: print("------------") print("Time", diff, "us") print("Temp", temp) print("V", vol, "ADC", adc) print("Vols", vols) print("adcs", adcs) print("diffs", diffs) if vol >= self._adc.maxVoltage(): await self._setValue("ec", None, log_error=False) await self._setValue("ppm", None, log_error=False) await _log.asyncLog("warn", "Cable not in fluid") else: if vol <= 0.5: _log.warn("Voltage <=0.5, change resistor") await self._setValue("ec", None, log_error=False) await self._setValue("ppm", None, log_error=False) return rc = (vol * (self._r1 + self._ra)) / (3.3 - vol) rc = rc - self._rg ec = 1000 / (rc * self._k) ec25 = ec / (1 + self._temp_coef * (temp - 25.0)) ppm = int(ec25 * self._ppm_conversion * 1000) if diff > self._to: await self._setValue("ec", None, log_error=False) await self._setValue("ppm", None, log_error=False) _log.warn( "Reading timeout, discarding value {!s}V, {!s}ppm".format( vol, ppm)) else: await self._setValue("ec", ec25) await self._setValue("ppm", ppm) if self.DEBUG: ec25 = round(ec25, 3) print("Rc", rc) print("EC", ec) print("EC25", ec25, "MilliSimens") print("PPM", ppm)
class WaterSensor(Component): DEBUG = False def __init__(self, adc, power_pin=None, cutoff_voltage=None, interval=None, interval_reading=1, topic=None, friendly_name=None): super().__init__() self._ir = interval_reading self._adc = ADC(adc) self._ppin = Pin(power_pin, machine.Pin.OUT) if power_pin is not None else None self._cv = cutoff_voltage or self._adc.maxVoltage() global _instances _instances.append(self) global _count self._t = topic or _mqtt.getDeviceTopic("waterSensor/{!s}".format(_count)) self._count = _count _count += 1 self._lv = None self._tm = time.ticks_ms() interval = interval or config.INTERVAL_SEND_SENSOR self._int = interval * 1000 self._frn = friendly_name async def _init(self): await super()._init() if self._count == 0: # only the first sensor reads all sensors to prevent uasyncio queue overflow interval_reading = self._ir - 0.05 * len(_instances) if interval_reading < 0: interval_reading = 0 # still has 100ms after every read while True: for inst in _instances: a = time.ticks_us() await inst.water() b = time.ticks_us() if WaterSensor.DEBUG: print("Water measurement took", (b - a) / 1000, "ms") await asyncio.sleep_ms(50) # using multiple sensors connected to Arduinos it would result in long blocking calls # because a single call to a pin takes ~17ms await asyncio.sleep(interval_reading) async def _discovery(self): name = "{!s}{!s}".format(_component_name, self._count) sens = DISCOVERY_BINARY_SENSOR.format("moisture") # device_class await self._publishDiscovery(_component_type, self._t, name, sens, self._frn or "Moisture") gc.collect() async def _read(self, publish=True): p = self._ppin if p is not None: p.value(1) vol = self._adc.readVoltage() if self.DEBUG is True: print("#{!s}, V".format(self._t[-1]), vol) if p is not None: p.value(0) if vol >= self._cv: state = False if publish is True and (time.ticks_diff(time.ticks_ms(), self._tm) > self._int or self._lv != state): await _mqtt.publish(self._t, "OFF", qos=1, retain=True) # dry self._tm = time.ticks_ms() self._lv = state return False else: state = True if publish is True and (time.ticks_diff(time.ticks_ms(), self._tm) > self._int or self._lv != state): await _mqtt.publish(self._t, "ON", qos=1, retain=True) # wet self._tm = time.ticks_ms() self._lv = state return True async def water(self, publish=True): return await self._read(publish)