# Author: Kevin Köck # Copyright Kevin Köck 2019 Released under the MIT license # Created on 2019-05-11 """ example config: { package: .sensors.phSensor component: PHsensor constructor_args: { adc: 22 # ADC object/pin number adc_multi: 1.52 # ADC multiplicator when using voltage divider (needed on esp when sensor probe not connected as voltage goes to 5V then) precision: 2 # precision of the pH value published voltage_calibration_0: 2.54 # voltage at pH value #0 pH_calibration_value_0: 6.86 # pH value for calibration point #0 voltage_calibration_1: 3.04 # voltage at pH value #1 pH_calibration_value_1: 4.01 # pH value for calibration point #1 # interval: 600 # optional, defaults to 600. -1 means do not automatically read sensor and publish values # mqtt_topic: sometopic # optional, defaults to home/<controller-id>/PHsensor # friendly_name: null # optional, friendly name shown in homeassistant gui with mqtt discovery } } Inspiration from: https://scidle.com/how-to-use-a-ph-sensor-with-arduino/ Example measurements: situation, real, esp shorted, 2.61 2.5 destilled 2.73 2.6 ph4.01 3.14 3.0 ph6.86 2.68 2.53 growing solution 3.24 3.1 (this is very wrong.., ph actually ~5.2) """ __updated__ = "2019-11-01" __version__ = "0.6" from pysmartnode import config from pysmartnode.components.machine.adc import ADC from pysmartnode import logging import uasyncio as asyncio from pysmartnode.utils.component import Component import gc COMPONENT_NAME = "PHsensor" _COMPONENT_TYPE = "sensor" _log = logging.getLogger(COMPONENT_NAME) _mqtt = config.getMQTT() gc.collect() _unit_index = -1 PH_TYPE = '"unit_of_meas":"pH",' \ '"val_tpl":"{{ value|float }}",' \ '"ic":"mdi:alpha-p-circle-outline"' _VAL_T_ACIDITY = "{{ value|float }}" class PHsensor(Component): def __init__(self, adc, adc_multi, voltage_calibration_0, pH_calibration_value_0, voltage_calibration_1, pH_calibration_value_1, precision=2, interval=None, mqtt_topic=None, friendly_name=None, discover=True): # This makes it possible to use multiple instances of MySensor global _unit_index _unit_index += 1 super().__init__(COMPONENT_NAME, __version__, _unit_index, discover) self._interval = interval or config.INTERVAL_SENSOR_PUBLISH self._topic = mqtt_topic self._frn = friendly_name self._adc = ADC(adc) self._adc_multi = adc_multi self.__ph = None self._prec = int(precision) self._v0 = voltage_calibration_0 self._v1 = voltage_calibration_1 self._ph0 = pH_calibration_value_0 self._ph1 = pH_calibration_value_1 gc.collect() if self._interval > 0: # if interval==-1 no loop will be started asyncio.get_event_loop().create_task(self._loop()) async def _loop(self): interval = self._interval while True: self.__ph = await self._read() await asyncio.sleep(interval) async def _discovery(self, register=True): name = "{!s}{!s}".format(COMPONENT_NAME, self._count) if register: await self._publishDiscovery(_COMPONENT_TYPE, self.acidityTopic(), name, PH_TYPE, self._frn or "pH") else: await self._deleteDiscovery(_COMPONENT_TYPE, name) async def _read(self, publish=True, timeout=5) -> float: buf = [] for _ in range(10): buf.append(self._adc.readVoltage() * self._adc_multi) await asyncio.sleep_ms(50) buf.remove(max(buf)) buf.remove(max(buf)) buf.remove(min(buf)) buf.remove(min(buf)) v = 0 for i in range(len(buf)): v += buf[i] v /= len(buf) ph1 = self._ph1 ph0 = self._ph0 v0 = self._v0 v1 = self._v1 m = (ph1 - ph0) / (v1 - v0) b = (ph0 * v1 - ph1 * v0) / (v1 - v0) print("U", v) print("m", m) print("b", b) value = m * v + b value = round(value, self._prec) print("pH", value) if value > 14: await _log.asyncLog("error", "Not correctly connected, voltage {!s}, ph {!s}".format(v, value)) return None if publish: await _mqtt.publish(self.acidityTopic(), ("{0:." + str(self._prec) + "f}").format(value), timeout=timeout, await_connection=False) return value async def acidity(self, publish=True, timeout=5, no_stale=False) -> float: if self._interval == -1 or no_stale: return await self._read(publish, timeout) return self.__ph @staticmethod def acidityTemplate(): """Other components like HVAC might need to know the value template of a sensor""" return _VAL_T_ACIDITY def acidityTopic(self): return self._topic or _mqtt.getDeviceTopic("{!s}{!s}".format(COMPONENT_NAME, self._count))