class Pmux: def __init__(self, s0, s1, s2, pin, s3=None, mux=None, pin_direction="OUT", pin_pull=None): """ It is possibile to initialize with: - pin numbers (or string on esp8266) - mux object and pin numbers (of mux pins) - Pin objects (either from machine or mux Pin objects [no mux object needed]) :type mux: Mux object if a multiplexer is used :type pin: pin number or string (esp8266) :type pin_direction: str of pin_direction s3 is optional, only needed if 16 pins are used, 8 pins possible with s0-s2. pmux can be read like a list: value=amux[2] pmux can be set like a list: amux[2]=1 """ if mux: self.s0 = s0 self.s1 = s1 self.s2 = s2 self.s3 = s3 self.mux = mux else: if type(s0) in (int, str): self.s0 = PyPin(s0, machine.Pin.OUT) self.s1 = PyPin(s1, machine.Pin.OUT) self.s2 = PyPin(s2, machine.Pin.OUT) if s3: self.s3 = PyPin(s3, machine.Pin.OUT) else: self.s0 = s0 self.s1 = s1 self.s2 = s2 if s3: self.s3 = s3 if s3: self.__size = 16 else: self.__size = 8 self._selected_pin = None if pin_direction not in dir(machine.Pin): raise TypeError( "Pin_direction {!r} does not exist".format(pin_direction)) if pin_pull not in dir(machine.Pin): raise TypeError("Pin_pull {!s} does not exist".format(pin_pull)) self.pin = PyPin(pin, getattr(machine.Pin, pin_direction), pin_pull) def __getitem__(self, a): return self.value(a) def __setitem__(self, a, direction): return self.value(a, direction) def getSize(self): """ Get number of pins""" return self.__size def _selectPin(self, a): if a >= self.__size: raise ValueError("Maximum Port number is {!s}".format(self.__size - 1)) if type(self.s0) == int: # mux pins if self.__size == 16: self.mux[self.s3] = (1 if a & 8 else 0) self.mux[self.s2] = (1 if a & 4 else 0) self.mux[self.s1] = (1 if a & 2 else 0) self.mux[self.s0] = (1 if a & 1 else 0) self.mux.write() else: if self.__size == 16: self.s3.value(1 if a & 8 else 0) self.s2.value(1 if a & 4 else 0) self.s1.value(1 if a & 2 else 0) self.s0.value(1 if a & 1 else 0) def value(self, a, value=None): if a != self._selected_pin: # only select pin if needed as it would slow IO-operations down and screw timings self._selectPin(a) if value is None: return self.pin.value() else: return self.pin.value(value) def mode(self, mode): if type(mode) == str: if mode not in dir(machine.Pin): raise TypeError("Mode {!r} is not available".format(mode)) mode = getattr(machine.Pin, mode) self.pin.mode(mode) def pull(self, p=None): return self.pin.pull(p) def drive(self, d=None): return self.pin.drive(d) def init(self, *args, **kwargs): self.pin.init(*args, **kwargs) def Pin(self, p): return Pin(self, p)
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 EC(Component): DEBUG = False def __init__(self, r1, ra, adc, power_pin, ground_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): # This makes it possible to use multiple instances of MySensor global _unit_index _unit_index += 1 super().__init__(COMPONENT_NAME, __version__, _unit_index) self._interval = interval or config.INTERVAL_SENSOR_PUBLISH self._prec_ec = int(precision_ec) self._adc = ADC(adc) self._ppin = Pin(power_pin, machine.Pin.OUT) self._gpin = Pin(ground_pin, machine.Pin.IN) # changing to OUTPUT GND when needed 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 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, register=True): sens = '"unit_of_meas":"mS",' \ '"val_tpl":"{{ value|float }}",' name = "{!s}{!s}{!s}".format(COMPONENT_NAME, self._count, "EC25") if register: await self._publishDiscovery(_COMPONENT_TYPE, self._topic_ec, name, sens, self._frn_ec or "EC25") else: await self._deleteDiscovery(_COMPONENT_TYPE, name) 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, timeout=5): if time.ticks_diff(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._gpin.init(mode=machine.Pin.OUT) self._gpin.value(0) 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 self._gpin.init(mode=machine.Pin.IN) 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 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), timeout=timeout, await_connection=False) await _mqtt.publish(self._topic_ppm, ppm, timeout=timeout, await_connection=False) return ec25, ppm async def ec(self, publish=True, timeout=5): if time.ticks_ms() - self._time > 5000: await self._read(publish, timeout) return self._ec25 async def ppm(self, publish=True, timeout=5): if time.ticks_diff(time.ticks_ms(), self._time) > 5000: await self._read(publish, timeout) return self._ppm