From 01f42949d468de6a4b453ad0aac44586e2a6b8c2 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:50:54 -0300 Subject: [PATCH 01/36] Traduzindo para PT-BR --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index bf44a45..f9cf577 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -181,7 +181,7 @@ async def on_stop(self): pass @parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Define se irá exibir o volume total ou fluxo em tempo real"), Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) class FlowSensor(CBPiSensor): From 6428554f88daf44bfefee2ab997591967282ebe5 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:11:47 -0300 Subject: [PATCH 02/36] =?UTF-8?q?Tradu=C3=A7=C3=A3o=20PTBR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Definida GPIO 21 como padrão Mais descrições traduzidas --- cbpi4-Flowmeter/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index f9cf577..0929633 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -143,7 +143,7 @@ def __init__(self, cbpi, id, props): @action(key="Reset Sensor", parameters=[]) async def Reset(self, **kwargs): await self.reset() - print("RESET FLOWSENSOR") + print("LEITURA REINICIADA") async def on_message(self, message): val = json.loads(message) @@ -180,9 +180,9 @@ async def on_stop(self): except asyncio.CancelledError: pass -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Define se irá exibir o volume total ou fluxo em tempo real"), - Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="A GPIO utilizada para conectar o fio de sinal do sensor"), + Property.Select(label="Formato da leitura", options=["Total volume", "Flow, unit/s"],description="Define se irá exibir o volume total ou fluxo em tempo real"), + Property.Number(label="Frequência", configurable=True, description="Define a frequência em hertz, o padrão é 7.5 e será usado para calibrar o seu sensor de acordo com a pressão de entrada.")]) class FlowSensor(CBPiSensor): @@ -190,7 +190,7 @@ def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0 self.fms = dict() - self.gpio=self.props.get("GPIO",0) + self.gpio=self.props.get("GPIO",21) self.sensorShow=self.props.get("Display","Total Volume") self.hertzProp=self.props.get("Hertz", 7.5) @@ -205,7 +205,7 @@ def __init__(self, cbpi, id, props): @action(key="Reset Sensor", parameters=[]) async def Reset(self, **kwargs): self.reset() - print("RESET FLOWSENSOR") + print("LEITURA REINICIADA") def get_unit(self): @@ -266,8 +266,8 @@ def get_state(self): ########## -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO uqe recebe o sinal do sensor"), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Defina quantas unidades serão contadas a cada impulso.")]) class VolumeSensor(CBPiSensor): From 1cbbb9aa5f5eaf5ddb0cd6505791af867539aaab Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:25:26 -0300 Subject: [PATCH 03/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 0929633..3b48559 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -44,7 +44,7 @@ async def init_sensor(self): self.version=plugin[0].get("Version","0.0.0") self.name=plugin[0].get("Name","cbpi4-Flowmeter") - self.flowmeter_update = self.cbpi.config.get(self.name+"_update", None) + self.flowmeter_update = self.cbpi.config.get(self.name"_update", None) unit = self.cbpi.config.get("flowunit", None) if unit is None: @@ -190,7 +190,7 @@ def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0 self.fms = dict() - self.gpio=self.props.get("GPIO",21) + self.gpio=self.props.get("GPIO",0) self.sensorShow=self.props.get("Display","Total Volume") self.hertzProp=self.props.get("Hertz", 7.5) From 5fc657dcea5567bf35a6463100fc6e3583886bea Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:40:05 -0300 Subject: [PATCH 04/36] Erro de leitura Linha 47 modificada --- cbpi4-Flowmeter/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 3b48559..eb8cd87 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -43,8 +43,8 @@ async def init_sensor(self): plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") self.version=plugin[0].get("Version","0.0.0") self.name=plugin[0].get("Name","cbpi4-Flowmeter") - - self.flowmeter_update = self.cbpi.config.get(self.name"_update", None) + + self.flowmeter_update = self.cbpi.config.get(self.name + "_update", None) unit = self.cbpi.config.get("flowunit", None) if unit is None: From 72c0ceba1bbbc89570f93b37dff162e4646708ad Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:57:20 -0300 Subject: [PATCH 05/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20codigo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 383 +++++++++--------------------------- 1 file changed, 98 insertions(+), 285 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index eb8cd87..fd26d83 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,100 +1,64 @@ - # -*- coding: utf-8 -*- import os from aiohttp import web import logging -from unittest.mock import MagicMock, patch import asyncio -import random -from cbpi.api import * import time +import json +from cbpi.api import * from cbpi.api.base import CBPiBase from cbpi.api import parameters, Property, action from cbpi.api.step import StepResult, CBPiStep from cbpi.api.timer import Timer -from voluptuous.schema_builder import message -from cbpi.api.dataclasses import NotificationAction, NotificationType -from cbpi.api.dataclasses import Sensor, Kettle, Props -import logging -from socket import timeout -from typing import KeysView +from cbpi.api.dataclasses import NotificationType from cbpi.api.config import ConfigType -import json logger = logging.getLogger(__name__) try: import RPi.GPIO as GPIO - mode = GPIO.getmode() - if (mode == None): + if GPIO.getmode() is None: GPIO.setmode(GPIO.BCM) - except Exception as e: - print(e) - pass + logger.error(f"Erro ao carregar RPi.GPIO: {e}") + GPIO = None class Flowmeter_Config(CBPiExtension): - - def __init__(self,cbpi): + def __init__(self, cbpi): self.cbpi = cbpi self._task = asyncio.create_task(self.init_sensor()) async def init_sensor(self): plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") - self.version=plugin[0].get("Version","0.0.0") - self.name=plugin[0].get("Name","cbpi4-Flowmeter") - - self.flowmeter_update = self.cbpi.config.get(self.name + "_update", None) + self.version = plugin[0].get("Version", "0.0.0") + self.name = plugin[0].get("Name", "cbpi4-Flowmeter") + + # CORRIGIDO: Indentação ajustada nesta linha + self.flowmeter_update = self.cbpi.config.get(self.name + "_update", None) unit = self.cbpi.config.get("flowunit", None) if unit is None: - logging.info("INIT FLOW SENSOR CONFIG") try: await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", - source=self.name, - options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) - + source=self.name, + options=[{"label": "L", "value": "L"}, + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - else: - if self.flowmeter_update == None or self.flowmeter_update != self.version: - try: - await self.cbpi.config.add("flowunit", unit , type=ConfigType.SELECT, description="Flowmeter unit", - source=self.name, - options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) - - except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - - if self.flowmeter_update == None or self.flowmeter_update != self.version: + logger.warning(f'Unable to add config: {e}') + + if self.flowmeter_update is None or self.flowmeter_update != self.version: try: await self.cbpi.config.add(self.name+"_update", self.version, type=ConfigType.STRING, description="Flowmeter Plugin Version", source='hidden') except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - pass - + logger.warning(f'Unable to update version config: {e}') class FlowMeterData(): SECONDS_IN_A_MINUTE = 60 MS_IN_A_SECOND = 1000.0 - enabled = True - clicks = 0 - lastClick = 0 - clickDelta = 0 - hertz = 0.0 - flow = 0 # in Liters per second - pour = 0.0 # in Liters def __init__(self): self.clicks = 0 @@ -106,295 +70,144 @@ def __init__(self): self.enabled = True def update(self, currentTime, hertzProp): - #print hertzProp self.clicks += 1 - # get the time delta self.clickDelta = max((currentTime - self.lastClick), 1) - # calculate the instantaneous speed + if self.enabled is True and self.clickDelta < 1000: self.hertz = FlowMeterData.MS_IN_A_SECOND / self.clickDelta - self.flow = self.hertz / (FlowMeterData.SECONDS_IN_A_MINUTE * hertzProp) # In Liters per second + # Fluxo em Unidades por segundo + self.flow = self.hertz / (hertzProp) instPour = self.flow * (self.clickDelta / FlowMeterData.MS_IN_A_SECOND) self.pour += instPour - # Update the last click + self.lastClick = currentTime def clear(self): self.pour = 0 + self.clicks = 0 return str(self.pour) - -@parameters([Property.Text(label="Topic", configurable=True, description="MQTT FlowSensor Topic"), - Property.Text(label="PayloadDictionary", configurable=True, default_value="", - description="Where to find msg in payload, leave blank for raw payload"), - Property.Text(label="ResetTopic", configurable=True, description="MQTT FlowSensor Reset Topic")]) -class MQTTFlowSensor(CBPiSensor): - - def __init__(self, cbpi, id, props): - super(MQTTFlowSensor, self).__init__(cbpi, id, props) - self.Topic = self.props.get("Topic", None) - self.payload_text = self.props.get("PayloadDictionary", None) - if self.payload_text != None: - self.payload_text = self.payload_text.split('.') - self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message) - self.value: float = 999 - self.ResetTopic = self.props.get("ResetTopic", None) - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - await self.reset() - print("LEITURA REINICIADA") - - async def on_message(self, message): - val = json.loads(message) - try: - if self.payload_text is not None: - for key in self.payload_text: - val = val.get(key, None) - - if isinstance(val, (int, float, str)): - self.value = float(val) - self.log_data(self.value) - self.push_update(self.value) - except Exception as e: - logging.info("MQTT Sensor Error {}".format(e)) - - async def run(self): - while self.running: - await asyncio.sleep(1) - - async def reset(self): - logging.info("Reset MQTTFlowsensor") - logging.info(self.ResetTopic) - await self.cbpi.satellite.publish(self.ResetTopic, json.dumps({}), True) - return "Ok" - - def get_state(self): - return dict(value=self.value) - - async def on_stop(self): - if self.mqtt_task.done() is False: - self.mqtt_task.cancel() - try: - await self.mqtt_task - except asyncio.CancelledError: - pass - -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="A GPIO utilizada para conectar o fio de sinal do sensor"), - Property.Select(label="Formato da leitura", options=["Total volume", "Flow, unit/s"],description="Define se irá exibir o volume total ou fluxo em tempo real"), - Property.Number(label="Frequência", configurable=True, description="Define a frequência em hertz, o padrão é 7.5 e será usado para calibrar o seu sensor de acordo com a pressão de entrada.")]) - +@parameters([ + Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="GPIO do sinal"), + Property.Select(label="Formato da leitura", options=["Total volume", "Flow, unit/s"], description="Exibição no painel"), + Property.Number(label="Frequência", configurable=True, default_value=7.5, description="Fator K do sensor (Hz por L/min)") +]) class FlowSensor(CBPiSensor): - def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0 - self.fms = dict() - self.gpio=self.props.get("GPIO",0) - self.sensorShow=self.props.get("Display","Total Volume") - self.hertzProp=self.props.get("Hertz", 7.5) + self.gpio = int(self.props.get("GPIO", 0)) + # CORRIGIDO: Chaves batendo com o label do @parameters + self.sensorShow = self.props.get("Formato da leitura", "Total volume") + self.hertzProp = float(self.props.get("Frequência", 7.5)) + + # CORRIGIDO: Inicialização do objeto antes do GPIO para evitar KeyError + self.fms = {self.gpio: FlowMeterData()} try: - GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) - GPIO.remove_event_detect(int(self.gpio)) - GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.doAClick, bouncetime=20) - self.fms[int(self.gpio)] = FlowMeterData() + if GPIO is not None: + GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) + GPIO.remove_event_detect(self.gpio) + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.doAClick, bouncetime=20) except Exception as e: - print(e) + logger.error(f"Erro GPIO FlowSensor: {e}") @action(key="Reset Sensor", parameters=[]) async def Reset(self, **kwargs): self.reset() - print("LEITURA REINICIADA") - - - def get_unit(self): - unit = self.cbpi.config.get("flowunit", "L") - if self.sensorShow == "Flow, unit/s": - unit = unit + "/s" - return unit def doAClick(self, channel): - currentTime = int(time.time() * FlowMeterData.MS_IN_A_SECOND) - hertzProp = self.hertzProp - self.fms[int(self.gpio)].update(currentTime, float(hertzProp)) + currentTime = int(time.time() * 1000) + if self.gpio in self.fms: + self.fms[self.gpio].update(currentTime, self.hertzProp) def convert(self, inputFlow): unit = self.cbpi.config.get("flowunit", "L") - if unit == "gal(us)": - inputFlow = inputFlow * 0.264172052 - elif unit == "gal(uk)": - inputFlow = inputFlow * 0.219969157 - elif unit == "qt": - inputFlow = inputFlow * 1.056688 - else: - pass - if self.sensorShow == "Flow, unit/s": - inputFlow = "{0:.2f}".format(inputFlow) - else: - inputFlow = "{0:.2f}".format(inputFlow) - return inputFlow + if unit == "gal(us)": inputFlow *= 0.264172 + elif unit == "gal(uk)": inputFlow *= 0.219969 + elif unit == "qt": inputFlow *= 1.056688 + return round(float(inputFlow), 2) async def run(self): while self.running is True: - if self.sensorShow == "Total volume": - flow = self.fms[int(self.gpio)].pour - flowConverted = self.convert(flow) - self.value= float(flowConverted) - elif self.sensorShow == "Flow, unit/s": - flow = self.fms[int(self.gpio)].flow - flowConverted = self.convert(flow) - self.value = float(flowConverted) - else: - logging.info("FlowSensor error") - - self.push_update(self.value) + if self.gpio in self.fms: + if self.sensorShow == "Total volume": + val = self.fms[self.gpio].pour + else: + val = self.fms[self.gpio].flow + + self.value = self.convert(val) + self.push_update(self.value) await asyncio.sleep(1) - def getValue(self): - flow = self.fms[int(self.gpio)].pour - flowConverted = self.convert(flow) - return flowConverted - def reset(self): - logging.info("Reset Flowsensor") - self.fms[int(self.gpio)].clear() - return "Ok" - - def get_state(self): - return dict(value=self.value) - -########## - -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO uqe recebe o sinal do sensor"), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Defina quantas unidades serão contadas a cada impulso.")]) + if self.gpio in self.fms: + self.fms[self.gpio].clear() + self.value = 0 + self.push_update(self.value) +@parameters([ + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, default_value=450) +]) class VolumeSensor(CBPiSensor): - def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) self.value = 0 self.impulses = 0 - self.liter = 0 - self.gpio = self.props.get("GPIO",0) - self.IperL = self.props.get("impulsesPerVolumeUnit", 450) - self.LperI = float(1.0) / float(self.IperL) + self.gpio = int(self.props.get("GPIO", 0)) + self.IperL = float(self.props.get("impulsesPerVolumeUnit", 450)) try: - GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) - GPIO.remove_event_detect(int(self.gpio)) - GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.impulseDetected, bouncetime=20) + if GPIO is not None: + GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.impulseDetected, bouncetime=20) except Exception as e: - print(e) - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - self.reset() - - @action(key="Fake Impulse", parameters=[]) - async def fakeImpulse(self, **kwargs): - self.impulseDetected("fake") + logger.error(f"Erro GPIO VolumeSensor: {e}") def impulseDetected(self, channel): - logging.debug("impulse detected on") - self.impulses = self.impulses + 1 - self.liter = round(self.LperI * self.impulses,3) - self.value = self.liter + self.impulses += 1 + self.value = round(self.impulses / self.IperL, 3) async def run(self): - while self.running is True: - self.log_data(self.value) + while self.running: self.push_update(self.value) await asyncio.sleep(1) def reset(self): - logging.info(f'reset VolumeSensor from {self.liter} to 0.') self.impulses = 0 - self.liter = 0 self.value = 0 - self.push_update(self.value) - return "Ok" - - def get_state(self): - return dict(value=self.value) - -########################################### - - -@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), - Property.Actor(label="Actor",description="Actor to switch media flow on and off"), - Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) + self.push_update(self.value) class FlowStep(CBPiStep): - - async def on_timer_done(self,timer): - self.summary = "" - self.cbpi.notify(self.name, 'Step finished. Transferred {} {}.'.format(round(self.current_volume,2),self.unit), NotificationType.SUCCESS) - if self.resetsensor == "Yes": - self.sensor.instance.reset() - - if self.actor is not None: - await self.actor_off(self.actor) - await self.next() - - async def on_timer_update(self,timer, seconds): - await self.push_update() + @parameters([ + Property.Number(label="Volume", configurable=True), + Property.Actor(label="Actor"), + Property.Sensor(label="Sensor"), + Property.Select(label="Reset", options=["Yes","No"]) + ]) + def __init__(self, cbpi, id, props): + super().__init__(cbpi, id, props) async def on_start(self): - self.unit = self.cbpi.config.get("flowunit", "L") - self.actor = self.props.get("Actor", None) - self.target_volume = float(self.props.get("Volume",0)) - self.flowsensor = self.props.get("Sensor",None) - logging.info(self.flowsensor) - self.sensor = self.get_sensor(self.flowsensor) - logging.info(self.sensor) - self.resetsensor = self.props.get("Reset","Yes") - - self.sensor.instance.reset() - if self.timer is None: - self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) - - async def on_stop(self): - if self.timer is not None: - await self.timer.stop() - self.summary = "" - if self.actor is not None: - await self.actor_off(self.actor) - await self.push_update() - - async def reset(self): - self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) - if self.actor is not None: - await self.actor_off(self.actor) - if self.resetsensor == "Yes": - self.sensor.instance.reset() - - + self.target_volume = float(self.props.get("Volume", 0)) + self.actor_id = self.props.get("Actor") + self.sensor_id = self.props.get("Sensor") + self.resetsensor = self.props.get("Reset", "Yes") + + if self.actor_id: await self.actor_on(self.actor_id) + async def run(self): - if self.actor is not None: - await self.actor_on(self.actor) - self.summary="" - await self.push_update() - while self.running == True: - self.current_volume = self.get_sensor_value(self.flowsensor).get("value") - self.summary="Volume: {}".format(self.current_volume) - await self.push_update() - - if self.current_volume >= self.target_volume and self.timer.is_running is not True: - self.timer.start() - self.timer.is_running = True - - await asyncio.sleep(0.2) - - return StepResult.DONE - + while self.running: + current_vol = self.get_sensor_value(self.sensor_id).get("value", 0) + if current_vol >= self.target_volume: + if self.actor_id: await self.actor_off(self.actor_id) + self.cbpi.notify("FlowStep", "Volume atingindo!", NotificationType.SUCCESS) + break + await asyncio.sleep(0.5) def setup(cbpi): cbpi.plugin.register("FlowStep", FlowStep) cbpi.plugin.register("VolumeSensor", VolumeSensor) cbpi.plugin.register("FlowSensor", FlowSensor) cbpi.plugin.register("Flowmeter_Config", Flowmeter_Config) - if str(cbpi.static_config.get("mqtt", False)).lower() == "true": - cbpi.plugin.register("MQTTFLowSensor", MQTTFlowSensor) - pass From 4b408fc5ac486051f20d5c26cbcc761dad259416 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:11:40 -0300 Subject: [PATCH 06/36] =?UTF-8?q?Corre=C3=A7=C3=B5es=20de=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 90 ++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index fd26d83..840a617 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- import os -from aiohttp import web -import logging import asyncio import time import json +import logging from cbpi.api import * from cbpi.api.base import CBPiBase from cbpi.api import parameters, Property, action @@ -33,7 +32,7 @@ async def init_sensor(self): self.version = plugin[0].get("Version", "0.0.0") self.name = plugin[0].get("Name", "cbpi4-Flowmeter") - # CORRIGIDO: Indentação ajustada nesta linha + # CORREÇÃO: Indentação corrigida aqui self.flowmeter_update = self.cbpi.config.get(self.name + "_update", None) unit = self.cbpi.config.get("flowunit", None) @@ -42,19 +41,17 @@ async def init_sensor(self): await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", source=self.name, options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) except Exception as e: - logger.warning(f'Unable to add config: {e}') - + logger.warning(f"Erro ao criar config flowunit: {e}") + if self.flowmeter_update is None or self.flowmeter_update != self.version: try: - await self.cbpi.config.add(self.name+"_update", self.version, type=ConfigType.STRING, - description="Flowmeter Plugin Version", - source='hidden') + await self.cbpi.config.add(self.name + "_update", self.version, type=ConfigType.STRING, description="Flowmeter Plugin Version", source='hidden') except Exception as e: - logger.warning(f'Unable to update version config: {e}') + logger.warning(f"Erro ao atualizar config versão: {e}") class FlowMeterData(): SECONDS_IN_A_MINUTE = 60 @@ -62,7 +59,7 @@ class FlowMeterData(): def __init__(self): self.clicks = 0 - self.lastClick = int(time.time() * FlowMeterData.MS_IN_A_SECOND) + self.lastClick = int(time.time() * 1000) self.clickDelta = 0 self.hertz = 0.0 self.flow = 0.0 @@ -72,45 +69,42 @@ def __init__(self): def update(self, currentTime, hertzProp): self.clicks += 1 self.clickDelta = max((currentTime - self.lastClick), 1) - if self.enabled is True and self.clickDelta < 1000: - self.hertz = FlowMeterData.MS_IN_A_SECOND / self.clickDelta - # Fluxo em Unidades por segundo - self.flow = self.hertz / (hertzProp) - instPour = self.flow * (self.clickDelta / FlowMeterData.MS_IN_A_SECOND) + self.hertz = self.MS_IN_A_SECOND / self.clickDelta + # Cálculo de fluxo em Unidades/Seg + self.flow = self.hertz / (hertzProp) + instPour = self.flow * (self.clickDelta / self.MS_IN_A_SECOND) self.pour += instPour - self.lastClick = currentTime def clear(self): - self.pour = 0 + self.pour = 0.0 self.clicks = 0 return str(self.pour) @parameters([ - Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="GPIO do sinal"), - Property.Select(label="Formato da leitura", options=["Total volume", "Flow, unit/s"], description="Exibição no painel"), - Property.Number(label="Frequência", configurable=True, default_value=7.5, description="Fator K do sensor (Hz por L/min)") + Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="GPIO de sinal do sensor"), + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"], description="O que exibir na tela"), + Property.Number(label="Hertz", configurable=True, default_value=7.5, description="Frequência de calibração (Fator K)") ]) class FlowSensor(CBPiSensor): def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) - self.value = 0 + self.value = 0.0 self.gpio = int(self.props.get("GPIO", 0)) - # CORRIGIDO: Chaves batendo com o label do @parameters - self.sensorShow = self.props.get("Formato da leitura", "Total volume") - self.hertzProp = float(self.props.get("Frequência", 7.5)) + self.sensorShow = self.props.get("Display", "Total volume") + self.hertzProp = float(self.props.get("Hertz", 7.5)) - # CORRIGIDO: Inicialização do objeto antes do GPIO para evitar KeyError + # CORREÇÃO: Inicialização do fms antes de configurar o hardware self.fms = {self.gpio: FlowMeterData()} try: - if GPIO is not None: + if GPIO: GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.remove_event_detect(self.gpio) GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.doAClick, bouncetime=20) except Exception as e: - logger.error(f"Erro GPIO FlowSensor: {e}") + logger.error(f"Erro GPIO FlowSensor {self.gpio}: {e}") @action(key="Reset Sensor", parameters=[]) async def Reset(self, **kwargs): @@ -121,21 +115,17 @@ def doAClick(self, channel): if self.gpio in self.fms: self.fms[self.gpio].update(currentTime, self.hertzProp) - def convert(self, inputFlow): + def convert(self, val): unit = self.cbpi.config.get("flowunit", "L") - if unit == "gal(us)": inputFlow *= 0.264172 - elif unit == "gal(uk)": inputFlow *= 0.219969 - elif unit == "qt": inputFlow *= 1.056688 - return round(float(inputFlow), 2) + if unit == "gal(us)": val *= 0.264172 + elif unit == "gal(uk)": val *= 0.219969 + elif unit == "qt": val *= 1.056688 + return round(float(val), 2) async def run(self): - while self.running is True: + while self.running: if self.gpio in self.fms: - if self.sensorShow == "Total volume": - val = self.fms[self.gpio].pour - else: - val = self.fms[self.gpio].flow - + val = self.fms[self.gpio].pour if self.sensorShow == "Total volume" else self.fms[self.gpio].flow self.value = self.convert(val) self.push_update(self.value) await asyncio.sleep(1) @@ -143,27 +133,27 @@ async def run(self): def reset(self): if self.gpio in self.fms: self.fms[self.gpio].clear() - self.value = 0 + self.value = 0.0 self.push_update(self.value) @parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), + Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]), Property.Number(label="impulsesPerVolumeUnit", configurable=True, default_value=450) ]) class VolumeSensor(CBPiSensor): def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) - self.value = 0 + self.value = 0.0 self.impulses = 0 self.gpio = int(self.props.get("GPIO", 0)) self.IperL = float(self.props.get("impulsesPerVolumeUnit", 450)) try: - if GPIO is not None: + if GPIO: GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.impulseDetected, bouncetime=20) except Exception as e: - logger.error(f"Erro GPIO VolumeSensor: {e}") + logger.error(f"Erro GPIO VolumeSensor {self.gpio}: {e}") def impulseDetected(self, channel): self.impulses += 1 @@ -176,7 +166,7 @@ async def run(self): def reset(self): self.impulses = 0 - self.value = 0 + self.value = 0.0 self.push_update(self.value) class FlowStep(CBPiStep): @@ -184,7 +174,7 @@ class FlowStep(CBPiStep): Property.Number(label="Volume", configurable=True), Property.Actor(label="Actor"), Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes","No"]) + Property.Select(label="Reset", options=["Yes", "No"]) ]) def __init__(self, cbpi, id, props): super().__init__(cbpi, id, props) @@ -194,15 +184,13 @@ async def on_start(self): self.actor_id = self.props.get("Actor") self.sensor_id = self.props.get("Sensor") self.resetsensor = self.props.get("Reset", "Yes") - if self.actor_id: await self.actor_on(self.actor_id) - + async def run(self): while self.running: current_vol = self.get_sensor_value(self.sensor_id).get("value", 0) if current_vol >= self.target_volume: if self.actor_id: await self.actor_off(self.actor_id) - self.cbpi.notify("FlowStep", "Volume atingindo!", NotificationType.SUCCESS) break await asyncio.sleep(0.5) From f669c924e55452c81c444614e422a818eda9fd2c Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:23:19 -0300 Subject: [PATCH 07/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20codigo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 246 +++++++++++++----------------------- 1 file changed, 88 insertions(+), 158 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 840a617..144cd72 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,66 +1,29 @@ # -*- coding: utf-8 -*- -import os -import asyncio import time import json import logging -from cbpi.api import * -from cbpi.api.base import CBPiBase -from cbpi.api import parameters, Property, action -from cbpi.api.step import StepResult, CBPiStep -from cbpi.api.timer import Timer -from cbpi.api.dataclasses import NotificationType -from cbpi.api.config import ConfigType +from modules import cbpi +from modules.core.hardware import ActorBase, SensorPassive +from modules.core.step import StepBase +from flask import Blueprint, render_template, jsonify, request +from modules.core.props import Property, StepProperty -logger = logging.getLogger(__name__) +blueprint = Blueprint('flowmeter', __name__) try: import RPi.GPIO as GPIO if GPIO.getmode() is None: GPIO.setmode(GPIO.BCM) except Exception as e: - logger.error(f"Erro ao carregar RPi.GPIO: {e}") - GPIO = None - -class Flowmeter_Config(CBPiExtension): - def __init__(self, cbpi): - self.cbpi = cbpi - self._task = asyncio.create_task(self.init_sensor()) - - async def init_sensor(self): - plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") - self.version = plugin[0].get("Version", "0.0.0") - self.name = plugin[0].get("Name", "cbpi4-Flowmeter") - - # CORREÇÃO: Indentação corrigida aqui - self.flowmeter_update = self.cbpi.config.get(self.name + "_update", None) - - unit = self.cbpi.config.get("flowunit", None) - if unit is None: - try: - await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", - source=self.name, - options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) - except Exception as e: - logger.warning(f"Erro ao criar config flowunit: {e}") - - if self.flowmeter_update is None or self.flowmeter_update != self.version: - try: - await self.cbpi.config.add(self.name + "_update", self.version, type=ConfigType.STRING, description="Flowmeter Plugin Version", source='hidden') - except Exception as e: - logger.warning(f"Erro ao atualizar config versão: {e}") + print("Erro ao carregar GPIO: %s" % e) class FlowMeterData(): - SECONDS_IN_A_MINUTE = 60 MS_IN_A_SECOND = 1000.0 + SECONDS_IN_A_MINUTE = 60 def __init__(self): self.clicks = 0 - self.lastClick = int(time.time() * 1000) - self.clickDelta = 0 + self.lastClick = int(time.time() * self.MS_IN_A_SECOND) self.hertz = 0.0 self.flow = 0.0 self.pour = 0.0 @@ -68,13 +31,15 @@ def __init__(self): def update(self, currentTime, hertzProp): self.clicks += 1 - self.clickDelta = max((currentTime - self.lastClick), 1) - if self.enabled is True and self.clickDelta < 1000: - self.hertz = self.MS_IN_A_SECOND / self.clickDelta - # Cálculo de fluxo em Unidades/Seg - self.flow = self.hertz / (hertzProp) - instPour = self.flow * (self.clickDelta / self.MS_IN_A_SECOND) + clickDelta = max((currentTime - self.lastClick), 1) + + if self.enabled and clickDelta < 1000: + self.hertz = self.MS_IN_A_SECOND / clickDelta + # Fluxo em L/s (Hertz / 7.5 / 60) + self.flow = self.hertz / (self.SECONDS_IN_A_MINUTE * hertzProp) + instPour = self.flow * (clickDelta / self.MS_IN_A_SECOND) self.pour += instPour + self.lastClick = currentTime def clear(self): @@ -82,120 +47,85 @@ def clear(self): self.clicks = 0 return str(self.pour) -@parameters([ - Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="GPIO de sinal do sensor"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"], description="O que exibir na tela"), - Property.Number(label="Hertz", configurable=True, default_value=7.5, description="Frequência de calibração (Fator K)") -]) -class FlowSensor(CBPiSensor): - def __init__(self, cbpi, id, props): - super(FlowSensor, self).__init__(cbpi, id, props) - self.value = 0.0 - self.gpio = int(self.props.get("GPIO", 0)) - self.sensorShow = self.props.get("Display", "Total volume") - self.hertzProp = float(self.props.get("Hertz", 7.5)) +@cbpi.sensor +class Flowmeter(SensorPassive): + gpio = Property.Select("GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) + sensorShow = Property.Select("Flowmeter display", options=["Total volume", "Flow, unit/s"]) + hertzProp = Property.Text("Hertz", configurable=True, default_value="7.5", description="Calibração: Hertz para 1L/min (Padrão 7.5)") + + def init(self): + # Inicializa os dados específicos desta instância de sensor + self.fms_data = FlowMeterData() - # CORREÇÃO: Inicialização do fms antes de configurar o hardware - self.fms = {self.gpio: FlowMeterData()} + # Garante que a unidade de medida exista no banco de dados + if cbpi.get_config_parameter("flowunit", None) is None: + cbpi.add_config_parameter("flowunit", "L", "select", "Flowmeter unit", options=["L", "gal(us)", "gal(uk)", "qt"]) try: - if GPIO: - GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - GPIO.remove_event_detect(self.gpio) - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.doAClick, bouncetime=20) + pin = int(self.gpio) + GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) + + # Limpa detecções antigas para evitar erros de "Edge already exists" + try: + GPIO.remove_event_detect(pin) + except: + pass + + GPIO.add_event_detect(pin, GPIO.RISING, callback=self.doAClick, bouncetime=20) except Exception as e: - logger.error(f"Erro GPIO FlowSensor {self.gpio}: {e}") - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - self.reset() + print("Erro ao configurar pino %s: %s" % (self.gpio, e)) def doAClick(self, channel): currentTime = int(time.time() * 1000) - if self.gpio in self.fms: - self.fms[self.gpio].update(currentTime, self.hertzProp) - - def convert(self, val): - unit = self.cbpi.config.get("flowunit", "L") - if unit == "gal(us)": val *= 0.264172 - elif unit == "gal(uk)": val *= 0.219969 - elif unit == "qt": val *= 1.056688 - return round(float(val), 2) - - async def run(self): - while self.running: - if self.gpio in self.fms: - val = self.fms[self.gpio].pour if self.sensorShow == "Total volume" else self.fms[self.gpio].flow - self.value = self.convert(val) - self.push_update(self.value) - await asyncio.sleep(1) - - def reset(self): - if self.gpio in self.fms: - self.fms[self.gpio].clear() - self.value = 0.0 - self.push_update(self.value) - -@parameters([ - Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, default_value=450) -]) -class VolumeSensor(CBPiSensor): - def __init__(self, cbpi, id, props): - super(VolumeSensor, self).__init__(cbpi, id, props) - self.value = 0.0 - self.impulses = 0 - self.gpio = int(self.props.get("GPIO", 0)) - self.IperL = float(self.props.get("impulsesPerVolumeUnit", 450)) - try: - if GPIO: - GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.impulseDetected, bouncetime=20) - except Exception as e: - logger.error(f"Erro GPIO VolumeSensor {self.gpio}: {e}") - - def impulseDetected(self, channel): - self.impulses += 1 - self.value = round(self.impulses / self.IperL, 3) + hz = float(self.hertzProp) + self.fms_data.update(currentTime, hz) + except: + self.fms_data.update(currentTime, 7.5) + + def convert(self, inputFlow): + unit = cbpi.get_config_parameter("flowunit", "L") + f_val = float(inputFlow) + + if unit == "gal(us)": f_val *= 0.264172 + elif unit == "gal(uk)": f_val *= 0.219969 + elif unit == "qt": f_val *= 1.056688 + + return "{0:.2f}".format(f_val) - async def run(self): - while self.running: - self.push_update(self.value) - await asyncio.sleep(1) + def read(self): + if self.sensorShow == "Total volume": + val = self.fms_data.pour + else: + val = self.fms_data.flow + + self.data_received(self.convert(val)) + @cbpi.action("Reset to zero") def reset(self): - self.impulses = 0 - self.value = 0.0 - self.push_update(self.value) - -class FlowStep(CBPiStep): - @parameters([ - Property.Number(label="Volume", configurable=True), - Property.Actor(label="Actor"), - Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes", "No"]) - ]) - def __init__(self, cbpi, id, props): - super().__init__(cbpi, id, props) - - async def on_start(self): - self.target_volume = float(self.props.get("Volume", 0)) - self.actor_id = self.props.get("Actor") - self.sensor_id = self.props.get("Sensor") - self.resetsensor = self.props.get("Reset", "Yes") - if self.actor_id: await self.actor_on(self.actor_id) - - async def run(self): - while self.running: - current_vol = self.get_sensor_value(self.sensor_id).get("value", 0) - if current_vol >= self.target_volume: - if self.actor_id: await self.actor_off(self.actor_id) - break - await asyncio.sleep(0.5) - -def setup(cbpi): - cbpi.plugin.register("FlowStep", FlowStep) - cbpi.plugin.register("VolumeSensor", VolumeSensor) - cbpi.plugin.register("FlowSensor", FlowSensor) - cbpi.plugin.register("Flowmeter_Config", Flowmeter_Config) + self.fms_data.clear() + self.data_received(0.0) + +@cbpi.step +class FlowmeterStep(StepBase): + sensor = StepProperty.Sensor("Sensor") + actorA = StepProperty.Actor("Actor") + volume = Property.Number("Volume", configurable=True) + + def init(self): + if self.actorA: + self.actor_on(int(self.actorA)) + + def execute(self): + sensor_id = int(self.sensor) + # Busca o valor atual lido pelo sensor no cache do CBPi + sensor_value = cbpi.cache.get("sensors").get(sensor_id).instance.fms_data.pour + + if float(sensor_value) >= float(self.volume): + if self.actorA: + self.actor_off(int(self.actorA)) + self.next() + +@cbpi.initalizer() +def init(cbpi): + cbpi.app.register_blueprint(blueprint, url_prefix='/api/flowmeter') From 4f23e7cf5272473d87cb2a34f534e2111212ce00 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:31:38 -0300 Subject: [PATCH 08/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 194 +++++++++++++++++------------------- 1 file changed, 90 insertions(+), 104 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 144cd72..6dcf933 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,131 +1,117 @@ # -*- coding: utf-8 -*- -import time -import json import logging -from modules import cbpi -from modules.core.hardware import ActorBase, SensorPassive -from modules.core.step import StepBase -from flask import Blueprint, render_template, jsonify, request -from modules.core.props import Property, StepProperty +import asyncio +import time +from cbpi.api import * +from cbpi.api.config import ConfigType -blueprint = Blueprint('flowmeter', __name__) +logger = logging.getLogger(__name__) try: import RPi.GPIO as GPIO if GPIO.getmode() is None: GPIO.setmode(GPIO.BCM) except Exception as e: - print("Erro ao carregar GPIO: %s" % e) + logger.error(f"Erro ao carregar RPi.GPIO: {e}") + GPIO = None class FlowMeterData(): - MS_IN_A_SECOND = 1000.0 - SECONDS_IN_A_MINUTE = 60 - def __init__(self): self.clicks = 0 - self.lastClick = int(time.time() * self.MS_IN_A_SECOND) - self.hertz = 0.0 + self.last_click = int(time.time() * 1000) self.flow = 0.0 self.pour = 0.0 - self.enabled = True - def update(self, currentTime, hertzProp): + def update(self, hertz_prop): + current_time = int(time.time() * 1000) self.clicks += 1 - clickDelta = max((currentTime - self.lastClick), 1) + delta = max((current_time - self.last_click), 1) - if self.enabled and clickDelta < 1000: - self.hertz = self.MS_IN_A_SECOND / clickDelta - # Fluxo em L/s (Hertz / 7.5 / 60) - self.flow = self.hertz / (self.SECONDS_IN_A_MINUTE * hertzProp) - instPour = self.flow * (clickDelta / self.MS_IN_A_SECOND) - self.pour += instPour + if delta < 1000: + # Hertz calculado pelo tempo entre pulsos + hertz = 1000.0 / delta + # Vazão: Hertz / Fator_K (hertz_prop) / 60 segundos + self.flow = hertz / (60.0 * hertz_prop) + # Incremento de volume + self.pour += self.flow * (delta / 1000.0) - self.lastClick = currentTime + self.last_click = current_time - def clear(self): + def reset(self): self.pour = 0.0 + self.flow = 0.0 self.clicks = 0 - return str(self.pour) - -@cbpi.sensor -class Flowmeter(SensorPassive): - gpio = Property.Select("GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) - sensorShow = Property.Select("Flowmeter display", options=["Total volume", "Flow, unit/s"]) - hertzProp = Property.Text("Hertz", configurable=True, default_value="7.5", description="Calibração: Hertz para 1L/min (Padrão 7.5)") - def init(self): - # Inicializa os dados específicos desta instância de sensor - self.fms_data = FlowMeterData() - - # Garante que a unidade de medida exista no banco de dados - if cbpi.get_config_parameter("flowunit", None) is None: - cbpi.add_config_parameter("flowunit", "L", "select", "Flowmeter unit", options=["L", "gal(us)", "gal(uk)", "qt"]) - - try: - pin = int(self.gpio) - GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) - - # Limpa detecções antigas para evitar erros de "Edge already exists" +@parameters([ + Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="Pino BCM de sinal"), + Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="O que mostrar no painel"), + Property.Number(label="Fator K (Hertz)", configurable=True, default_value=7.5, description="Frequência para 1 L/min (Padrão YF-S201: 7.5)") +]) +class FlowSensor(CBPiSensor): + def __init__(self, cbpi, id, props): + super(FlowSensor, self).__init__(cbpi, id, props) + self.value = 0.0 + self.gpio = int(self.props.get("GPIO", 0)) + self.display_mode = self.props.get("Exibição", "Volume Total") + self.k_factor = float(self.props.get("Fator K (Hertz)", 7.5)) + self.data = FlowMeterData() + + if GPIO: try: - GPIO.remove_event_detect(pin) - except: - pass - - GPIO.add_event_detect(pin, GPIO.RISING, callback=self.doAClick, bouncetime=20) - except Exception as e: - print("Erro ao configurar pino %s: %s" % (self.gpio, e)) - - def doAClick(self, channel): - currentTime = int(time.time() * 1000) - try: - hz = float(self.hertzProp) - self.fms_data.update(currentTime, hz) - except: - self.fms_data.update(currentTime, 7.5) - - def convert(self, inputFlow): - unit = cbpi.get_config_parameter("flowunit", "L") - f_val = float(inputFlow) - - if unit == "gal(us)": f_val *= 0.264172 - elif unit == "gal(uk)": f_val *= 0.219969 - elif unit == "qt": f_val *= 1.056688 - - return "{0:.2f}".format(f_val) - - def read(self): - if self.sensorShow == "Total volume": - val = self.fms_data.pour - else: - val = self.fms_data.flow + GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse_callback, bouncetime=20) + except Exception as e: + logger.error(f"Erro no GPIO {self.gpio}: {e}") + + def pulse_callback(self, channel): + self.data.update(self.k_factor) + + @action(key="Resetar Volume", parameters=[]) + async def reset_volume(self, **kwargs): + self.data.reset() + self.value = 0.0 + self.push_update(0.0) + + async def run(self): + while self.running: + if self.display_mode == "Volume Total": + self.value = round(self.data.pour, 2) + else: + self.value = round(self.data.flow, 3) - self.data_received(self.convert(val)) - - @cbpi.action("Reset to zero") - def reset(self): - self.fms_data.clear() - self.data_received(0.0) - -@cbpi.step -class FlowmeterStep(StepBase): - sensor = StepProperty.Sensor("Sensor") - actorA = StepProperty.Actor("Actor") - volume = Property.Number("Volume", configurable=True) - - def init(self): - if self.actorA: - self.actor_on(int(self.actorA)) - - def execute(self): - sensor_id = int(self.sensor) - # Busca o valor atual lido pelo sensor no cache do CBPi - sensor_value = cbpi.cache.get("sensors").get(sensor_id).instance.fms_data.pour + self.push_update(self.value) + await asyncio.sleep(1) + + def get_unit(self): + return "L" if self.display_mode == "Volume Total" else "L/s" + +class FlowStep(CBPiStep): + @parameters([ + Property.Sensor(label="Sensor de Fluxo"), + Property.Actor(label="Bomba/Válvula"), + Property.Number(label="Volume Alvo (L)", configurable=True, default_value=10) + ]) + def __init__(self, cbpi, id, props): + super().__init__(cbpi, id, props) + self.target = float(self.props.get("Volume Alvo (L)", 10)) + self.sensor_id = self.props.get("Sensor de Fluxo") + self.actor_id = self.props.get("Bomba/Válvula") + + async def run(self): + if self.actor_id: + await self.actor_on(self.actor_id) - if float(sensor_value) >= float(self.volume): - if self.actorA: - self.actor_off(int(self.actorA)) - self.next() + while self.running: + # Obtém valor atual do sensor + current_vol = self.get_sensor_value(self.sensor_id).get("value", 0) + + if current_vol >= self.target: + if self.actor_id: + await self.actor_off(self.actor_id) + break + + await asyncio.sleep(0.5) -@cbpi.initalizer() -def init(cbpi): - cbpi.app.register_blueprint(blueprint, url_prefix='/api/flowmeter') +def setup(cbpi): + cbpi.plugin.register("FlowSensor", FlowSensor) + cbpi.plugin.register("FlowStep", FlowStep) From 5a79dc63665c56a0f2122dfc14b8975bc5ff3f80 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:42:56 -0300 Subject: [PATCH 09/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 79 +++++++++++-------------------------- 1 file changed, 23 insertions(+), 56 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 6dcf933..d05457c 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,21 +1,24 @@ # -*- coding: utf-8 -*- -import logging import asyncio +import logging import time from cbpi.api import * -from cbpi.api.config import ConfigType +# Configuração do Logger para ver erros no terminal logger = logging.getLogger(__name__) +# Tenta importar GPIO, se falhar (ex: rodando no PC), não trava o plugin try: import RPi.GPIO as GPIO if GPIO.getmode() is None: GPIO.setmode(GPIO.BCM) + GPIO_AVAILABLE = True except Exception as e: - logger.error(f"Erro ao carregar RPi.GPIO: {e}") + logger.error(f"FLOWMETER: RPi.GPIO não encontrado. O plugin rodará em modo simulado. Erro: {e}") + GPIO_AVAILABLE = False GPIO = None -class FlowMeterData(): +class FlowMeterData: def __init__(self): self.clicks = 0 self.last_click = int(time.time() * 1000) @@ -26,15 +29,10 @@ def update(self, hertz_prop): current_time = int(time.time() * 1000) self.clicks += 1 delta = max((current_time - self.last_click), 1) - if delta < 1000: - # Hertz calculado pelo tempo entre pulsos hertz = 1000.0 / delta - # Vazão: Hertz / Fator_K (hertz_prop) / 60 segundos self.flow = hertz / (60.0 * hertz_prop) - # Incremento de volume self.pour += self.flow * (delta / 1000.0) - self.last_click = current_time def reset(self): @@ -43,75 +41,44 @@ def reset(self): self.clicks = 0 @parameters([ - Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="Pino BCM de sinal"), - Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="O que mostrar no painel"), - Property.Number(label="Fator K (Hertz)", configurable=True, default_value=7.5, description="Frequência para 1 L/min (Padrão YF-S201: 7.5)") + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO BCM"), + Property.Select(label="Mode", options=["Volume Total", "Fluxo L/s"], description="Modo de Exibição"), + Property.Number(label="K Factor", configurable=True, default_value=7.5, description="Frequencia (Hz) para 1 L/min") ]) class FlowSensor(CBPiSensor): def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0.0 self.gpio = int(self.props.get("GPIO", 0)) - self.display_mode = self.props.get("Exibição", "Volume Total") - self.k_factor = float(self.props.get("Fator K (Hertz)", 7.5)) + self.mode = self.props.get("Mode", "Volume Total") + self.k_factor = float(self.props.get("K Factor", 7.5)) self.data = FlowMeterData() - if GPIO: + if GPIO_AVAILABLE: try: GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse_callback, bouncetime=20) + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse, bouncetime=20) + logger.info(f"FLOWMETER: GPIO {self.gpio} configurada com sucesso.") except Exception as e: - logger.error(f"Erro no GPIO {self.gpio}: {e}") + logger.error(f"FLOWMETER: Erro ao configurar GPIO {self.gpio}: {e}") - def pulse_callback(self, channel): + def pulse(self, channel): self.data.update(self.k_factor) - @action(key="Resetar Volume", parameters=[]) - async def reset_volume(self, **kwargs): + @action(key="Reset", parameters=[]) + async def reset_vol(self, **kwargs): self.data.reset() self.value = 0.0 self.push_update(0.0) async def run(self): while self.running: - if self.display_mode == "Volume Total": - self.value = round(self.data.pour, 2) - else: - self.value = round(self.data.flow, 3) - + val = self.data.pour if self.mode == "Volume Total" else self.data.flow + self.value = round(val, 2) self.push_update(self.value) await asyncio.sleep(1) - def get_unit(self): - return "L" if self.display_mode == "Volume Total" else "L/s" - -class FlowStep(CBPiStep): - @parameters([ - Property.Sensor(label="Sensor de Fluxo"), - Property.Actor(label="Bomba/Válvula"), - Property.Number(label="Volume Alvo (L)", configurable=True, default_value=10) - ]) - def __init__(self, cbpi, id, props): - super().__init__(cbpi, id, props) - self.target = float(self.props.get("Volume Alvo (L)", 10)) - self.sensor_id = self.props.get("Sensor de Fluxo") - self.actor_id = self.props.get("Bomba/Válvula") - - async def run(self): - if self.actor_id: - await self.actor_on(self.actor_id) - - while self.running: - # Obtém valor atual do sensor - current_vol = self.get_sensor_value(self.sensor_id).get("value", 0) - - if current_vol >= self.target: - if self.actor_id: - await self.actor_off(self.actor_id) - break - - await asyncio.sleep(0.5) - +# --- ESTA PARTE É CRUCIAL PARA APARECER NO MENU --- def setup(cbpi): cbpi.plugin.register("FlowSensor", FlowSensor) - cbpi.plugin.register("FlowStep", FlowStep) + pass From 85ea49e673c5ec81fff3ad2ce43cca53d1fc0674 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:50:10 -0300 Subject: [PATCH 10/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 129 ++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 19 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index d05457c..712ba7e 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,24 +1,26 @@ # -*- coding: utf-8 -*- -import asyncio import logging +import asyncio import time from cbpi.api import * +from cbpi.api.config import ConfigType -# Configuração do Logger para ver erros no terminal +# Configuração de Log logger = logging.getLogger(__name__) -# Tenta importar GPIO, se falhar (ex: rodando no PC), não trava o plugin +# Tenta carregar a biblioteca GPIO de forma segura try: import RPi.GPIO as GPIO if GPIO.getmode() is None: GPIO.setmode(GPIO.BCM) GPIO_AVAILABLE = True except Exception as e: - logger.error(f"FLOWMETER: RPi.GPIO não encontrado. O plugin rodará em modo simulado. Erro: {e}") + logger.error(f"FLOWMETER: RPi.GPIO não encontrado (Modo Simulação). Erro: {e}") GPIO_AVAILABLE = False GPIO = None -class FlowMeterData: +# --- CLASSE DE DADOS (Lógica Matemática) --- +class FlowMeterData(): def __init__(self): self.clicks = 0 self.last_click = int(time.time() * 1000) @@ -28,11 +30,20 @@ def __init__(self): def update(self, hertz_prop): current_time = int(time.time() * 1000) self.clicks += 1 + # Calcula tempo desde o último pulso delta = max((current_time - self.last_click), 1) + + # Filtro de ruído: ignora pulsos excessivamente rápidos (<1ms) if delta < 1000: + # Frequência (Hz) = 1000ms / delta_ms hertz = 1000.0 / delta + + # Vazão (L/s) = Frequência / Fator K / 60 self.flow = hertz / (60.0 * hertz_prop) + + # Volume (L) = Vazão * (tempo_do_pulso / 1000) self.pour += self.flow * (delta / 1000.0) + self.last_click = current_time def reset(self): @@ -40,45 +51,125 @@ def reset(self): self.flow = 0.0 self.clicks = 0 +# --- SENSOR PRINCIPAL (FlowSensor) --- @parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO BCM"), - Property.Select(label="Mode", options=["Volume Total", "Fluxo L/s"], description="Modo de Exibição"), - Property.Number(label="K Factor", configurable=True, default_value=7.5, description="Frequencia (Hz) para 1 L/min") + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="Pino BCM (Sinal)"), + Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="Modo de visualização no Dashboard"), + Property.Number(label="Fator K", configurable=True, default_value=7.5, description="Calibração (Hz para 1 L/min). YF-S201 usa 7.5") ]) class FlowSensor(CBPiSensor): def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0.0 self.gpio = int(self.props.get("GPIO", 0)) - self.mode = self.props.get("Mode", "Volume Total") - self.k_factor = float(self.props.get("K Factor", 7.5)) + self.display_mode = self.props.get("Exibição", "Volume Total") + self.k_factor = float(self.props.get("Fator K", 7.5)) + + # Inicializa lógica matemática self.data = FlowMeterData() + # Configuração do Hardware if GPIO_AVAILABLE: try: GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse, bouncetime=20) - logger.info(f"FLOWMETER: GPIO {self.gpio} configurada com sucesso.") + # Remove eventos anteriores para evitar conflitos se o sensor for editado + try: + GPIO.remove_event_detect(self.gpio) + except: + pass + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse_callback, bouncetime=20) + logger.info(f"FLOWMETER: GPIO {self.gpio} iniciada com sucesso.") except Exception as e: - logger.error(f"FLOWMETER: Erro ao configurar GPIO {self.gpio}: {e}") + logger.error(f"FLOWMETER: Falha ao iniciar GPIO {self.gpio}: {e}") - def pulse(self, channel): + # Callback executado a cada pulso elétrico + def pulse_callback(self, channel): self.data.update(self.k_factor) - @action(key="Reset", parameters=[]) - async def reset_vol(self, **kwargs): + # Ação do botão de reset na interface + @action(key="Zerar Volume", parameters=[]) + async def reset_volume(self, **kwargs): self.data.reset() self.value = 0.0 self.push_update(0.0) + # Loop principal de atualização da interface (roda a cada 1 seg) async def run(self): while self.running: - val = self.data.pour if self.mode == "Volume Total" else self.data.flow + if self.display_mode == "Volume Total": + val = self.data.pour + else: + val = self.data.flow + self.value = round(val, 2) self.push_update(self.value) await asyncio.sleep(1) -# --- ESTA PARTE É CRUCIAL PARA APARECER NO MENU --- +# --- SENSOR DE VOLUME SIMPLES (Opcional) --- +@parameters([ + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), + Property.Number(label="Pulsos por Litro", configurable=True, default_value=450) +]) +class VolumeSensor(CBPiSensor): + def __init__(self, cbpi, id, props): + super(VolumeSensor, self).__init__(cbpi, id, props) + self.value = 0.0 + self.impulses = 0 + self.gpio = int(self.props.get("GPIO", 0)) + self.pulsos_litro = float(self.props.get("Pulsos por Litro", 450)) + + if GPIO_AVAILABLE: + try: + GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) + GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.count, bouncetime=20) + except Exception as e: + logger.error(f"VOLUMESENSOR: Erro GPIO {self.gpio}: {e}") + + def count(self, channel): + self.impulses += 1 + self.value = round(self.impulses / self.pulsos_litro, 3) + + async def run(self): + while self.running: + self.push_update(self.value) + await asyncio.sleep(1) + +# --- PASSO AUTOMÁTICO (FlowStep) --- +class FlowStep(CBPiStep): + @parameters([ + Property.Sensor(label="Sensor de Fluxo"), + Property.Actor(label="Bomba/Valvula"), + Property.Number(label="Volume Alvo (L)", configurable=True, default_value=5.0) + ]) + def __init__(self, cbpi, id, props): + super().__init__(cbpi, id, props) + self.target = float(self.props.get("Volume Alvo (L)", 5.0)) + self.sensor_id = self.props.get("Sensor de Fluxo") + self.actor_id = self.props.get("Bomba/Valvula") + + async def run(self): + # Liga a bomba se houver uma configurada + if self.actor_id: + await self.actor_on(self.actor_id) + + while self.running: + try: + # Lê o valor atual do sensor selecionado + current_vol = float(self.get_sensor_value(self.sensor_id).get("value", 0)) + + # Verifica se atingiu o alvo + if current_vol >= self.target: + if self.actor_id: + await self.actor_off(self.actor_id) + self.cbpi.notify("FlowStep", f"Transferencia de {current_vol}L concluida!", type=NotificationType.SUCCESS) + break + except Exception as e: + logger.error(f"FLOWSTEP Error: {e}") + + await asyncio.sleep(0.5) + +# --- REGISTRO DOS PLUGINS --- def setup(cbpi): cbpi.plugin.register("FlowSensor", FlowSensor) - pass + cbpi.plugin.register("VolumeSensor", VolumeSensor) + cbpi.plugin.register("FlowStep", FlowStep) From 962a364951f50186591e52c0a43c6d17b05716fa Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:54:55 -0300 Subject: [PATCH 11/36] =?UTF-8?q?Configura=C3=A7=C3=A3o=20inicial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 475 ++++++++++++++++++++++++++---------- 1 file changed, 350 insertions(+), 125 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 712ba7e..bf44a45 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,175 +1,400 @@ + # -*- coding: utf-8 -*- +import os +from aiohttp import web import logging +from unittest.mock import MagicMock, patch import asyncio -import time +import random from cbpi.api import * +import time +from cbpi.api.base import CBPiBase +from cbpi.api import parameters, Property, action +from cbpi.api.step import StepResult, CBPiStep +from cbpi.api.timer import Timer +from voluptuous.schema_builder import message +from cbpi.api.dataclasses import NotificationAction, NotificationType +from cbpi.api.dataclasses import Sensor, Kettle, Props +import logging +from socket import timeout +from typing import KeysView from cbpi.api.config import ConfigType +import json -# Configuração de Log logger = logging.getLogger(__name__) -# Tenta carregar a biblioteca GPIO de forma segura try: import RPi.GPIO as GPIO - if GPIO.getmode() is None: + mode = GPIO.getmode() + if (mode == None): GPIO.setmode(GPIO.BCM) - GPIO_AVAILABLE = True + except Exception as e: - logger.error(f"FLOWMETER: RPi.GPIO não encontrado (Modo Simulação). Erro: {e}") - GPIO_AVAILABLE = False - GPIO = None + print(e) + pass + +class Flowmeter_Config(CBPiExtension): + + def __init__(self,cbpi): + self.cbpi = cbpi + self._task = asyncio.create_task(self.init_sensor()) + + async def init_sensor(self): + plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") + self.version=plugin[0].get("Version","0.0.0") + self.name=plugin[0].get("Name","cbpi4-Flowmeter") + + self.flowmeter_update = self.cbpi.config.get(self.name+"_update", None) + + unit = self.cbpi.config.get("flowunit", None) + if unit is None: + logging.info("INIT FLOW SENSOR CONFIG") + try: + await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", + source=self.name, + options=[{"label": "L", "value": "L"}, + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) + + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + else: + if self.flowmeter_update == None or self.flowmeter_update != self.version: + try: + await self.cbpi.config.add("flowunit", unit , type=ConfigType.SELECT, description="Flowmeter unit", + source=self.name, + options=[{"label": "L", "value": "L"}, + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) + + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + + if self.flowmeter_update == None or self.flowmeter_update != self.version: + try: + await self.cbpi.config.add(self.name+"_update", self.version, type=ConfigType.STRING, + description="Flowmeter Plugin Version", + source='hidden') + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + pass + -# --- CLASSE DE DADOS (Lógica Matemática) --- class FlowMeterData(): + SECONDS_IN_A_MINUTE = 60 + MS_IN_A_SECOND = 1000.0 + enabled = True + clicks = 0 + lastClick = 0 + clickDelta = 0 + hertz = 0.0 + flow = 0 # in Liters per second + pour = 0.0 # in Liters + def __init__(self): self.clicks = 0 - self.last_click = int(time.time() * 1000) + self.lastClick = int(time.time() * FlowMeterData.MS_IN_A_SECOND) + self.clickDelta = 0 + self.hertz = 0.0 self.flow = 0.0 self.pour = 0.0 + self.enabled = True - def update(self, hertz_prop): - current_time = int(time.time() * 1000) + def update(self, currentTime, hertzProp): + #print hertzProp self.clicks += 1 - # Calcula tempo desde o último pulso - delta = max((current_time - self.last_click), 1) - - # Filtro de ruído: ignora pulsos excessivamente rápidos (<1ms) - if delta < 1000: - # Frequência (Hz) = 1000ms / delta_ms - hertz = 1000.0 / delta - - # Vazão (L/s) = Frequência / Fator K / 60 - self.flow = hertz / (60.0 * hertz_prop) - - # Volume (L) = Vazão * (tempo_do_pulso / 1000) - self.pour += self.flow * (delta / 1000.0) - - self.last_click = current_time + # get the time delta + self.clickDelta = max((currentTime - self.lastClick), 1) + # calculate the instantaneous speed + if self.enabled is True and self.clickDelta < 1000: + self.hertz = FlowMeterData.MS_IN_A_SECOND / self.clickDelta + self.flow = self.hertz / (FlowMeterData.SECONDS_IN_A_MINUTE * hertzProp) # In Liters per second + instPour = self.flow * (self.clickDelta / FlowMeterData.MS_IN_A_SECOND) + self.pour += instPour + # Update the last click + self.lastClick = currentTime - def reset(self): - self.pour = 0.0 - self.flow = 0.0 - self.clicks = 0 + def clear(self): + self.pour = 0 + return str(self.pour) + + +@parameters([Property.Text(label="Topic", configurable=True, description="MQTT FlowSensor Topic"), + Property.Text(label="PayloadDictionary", configurable=True, default_value="", + description="Where to find msg in payload, leave blank for raw payload"), + Property.Text(label="ResetTopic", configurable=True, description="MQTT FlowSensor Reset Topic")]) +class MQTTFlowSensor(CBPiSensor): + + def __init__(self, cbpi, id, props): + super(MQTTFlowSensor, self).__init__(cbpi, id, props) + self.Topic = self.props.get("Topic", None) + self.payload_text = self.props.get("PayloadDictionary", None) + if self.payload_text != None: + self.payload_text = self.payload_text.split('.') + self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message) + self.value: float = 999 + self.ResetTopic = self.props.get("ResetTopic", None) + + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + await self.reset() + print("RESET FLOWSENSOR") + + async def on_message(self, message): + val = json.loads(message) + try: + if self.payload_text is not None: + for key in self.payload_text: + val = val.get(key, None) + + if isinstance(val, (int, float, str)): + self.value = float(val) + self.log_data(self.value) + self.push_update(self.value) + except Exception as e: + logging.info("MQTT Sensor Error {}".format(e)) + + async def run(self): + while self.running: + await asyncio.sleep(1) + + async def reset(self): + logging.info("Reset MQTTFlowsensor") + logging.info(self.ResetTopic) + await self.cbpi.satellite.publish(self.ResetTopic, json.dumps({}), True) + return "Ok" + + def get_state(self): + return dict(value=self.value) + + async def on_stop(self): + if self.mqtt_task.done() is False: + self.mqtt_task.cancel() + try: + await self.mqtt_task + except asyncio.CancelledError: + pass + +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), + Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) -# --- SENSOR PRINCIPAL (FlowSensor) --- -@parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="Pino BCM (Sinal)"), - Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="Modo de visualização no Dashboard"), - Property.Number(label="Fator K", configurable=True, default_value=7.5, description="Calibração (Hz para 1 L/min). YF-S201 usa 7.5") -]) class FlowSensor(CBPiSensor): + def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) - self.value = 0.0 - self.gpio = int(self.props.get("GPIO", 0)) - self.display_mode = self.props.get("Exibição", "Volume Total") - self.k_factor = float(self.props.get("Fator K", 7.5)) - - # Inicializa lógica matemática - self.data = FlowMeterData() - - # Configuração do Hardware - if GPIO_AVAILABLE: - try: - GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - # Remove eventos anteriores para evitar conflitos se o sensor for editado - try: - GPIO.remove_event_detect(self.gpio) - except: - pass - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.pulse_callback, bouncetime=20) - logger.info(f"FLOWMETER: GPIO {self.gpio} iniciada com sucesso.") - except Exception as e: - logger.error(f"FLOWMETER: Falha ao iniciar GPIO {self.gpio}: {e}") + self.value = 0 + self.fms = dict() + self.gpio=self.props.get("GPIO",0) + self.sensorShow=self.props.get("Display","Total Volume") + self.hertzProp=self.props.get("Hertz", 7.5) + + try: + GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) + GPIO.remove_event_detect(int(self.gpio)) + GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.doAClick, bouncetime=20) + self.fms[int(self.gpio)] = FlowMeterData() + except Exception as e: + print(e) - # Callback executado a cada pulso elétrico - def pulse_callback(self, channel): - self.data.update(self.k_factor) + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + self.reset() + print("RESET FLOWSENSOR") + - # Ação do botão de reset na interface - @action(key="Zerar Volume", parameters=[]) - async def reset_volume(self, **kwargs): - self.data.reset() - self.value = 0.0 - self.push_update(0.0) + def get_unit(self): + unit = self.cbpi.config.get("flowunit", "L") + if self.sensorShow == "Flow, unit/s": + unit = unit + "/s" + return unit + + def doAClick(self, channel): + currentTime = int(time.time() * FlowMeterData.MS_IN_A_SECOND) + hertzProp = self.hertzProp + self.fms[int(self.gpio)].update(currentTime, float(hertzProp)) + + def convert(self, inputFlow): + unit = self.cbpi.config.get("flowunit", "L") + if unit == "gal(us)": + inputFlow = inputFlow * 0.264172052 + elif unit == "gal(uk)": + inputFlow = inputFlow * 0.219969157 + elif unit == "qt": + inputFlow = inputFlow * 1.056688 + else: + pass + if self.sensorShow == "Flow, unit/s": + inputFlow = "{0:.2f}".format(inputFlow) + else: + inputFlow = "{0:.2f}".format(inputFlow) + return inputFlow - # Loop principal de atualização da interface (roda a cada 1 seg) async def run(self): - while self.running: - if self.display_mode == "Volume Total": - val = self.data.pour + while self.running is True: + if self.sensorShow == "Total volume": + flow = self.fms[int(self.gpio)].pour + flowConverted = self.convert(flow) + self.value= float(flowConverted) + elif self.sensorShow == "Flow, unit/s": + flow = self.fms[int(self.gpio)].flow + flowConverted = self.convert(flow) + self.value = float(flowConverted) else: - val = self.data.flow - - self.value = round(val, 2) + logging.info("FlowSensor error") + self.push_update(self.value) await asyncio.sleep(1) -# --- SENSOR DE VOLUME SIMPLES (Opcional) --- -@parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), - Property.Number(label="Pulsos por Litro", configurable=True, default_value=450) -]) + def getValue(self): + flow = self.fms[int(self.gpio)].pour + flowConverted = self.convert(flow) + return flowConverted + + def reset(self): + logging.info("Reset Flowsensor") + self.fms[int(self.gpio)].clear() + return "Ok" + + def get_state(self): + return dict(value=self.value) + +########## + +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) + class VolumeSensor(CBPiSensor): + def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) - self.value = 0.0 + self.value = 0 self.impulses = 0 - self.gpio = int(self.props.get("GPIO", 0)) - self.pulsos_litro = float(self.props.get("Pulsos por Litro", 450)) + self.liter = 0 + self.gpio = self.props.get("GPIO",0) + self.IperL = self.props.get("impulsesPerVolumeUnit", 450) + self.LperI = float(1.0) / float(self.IperL) - if GPIO_AVAILABLE: - try: - GPIO.setup(self.gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP) - GPIO.add_event_detect(self.gpio, GPIO.RISING, callback=self.count, bouncetime=20) - except Exception as e: - logger.error(f"VOLUMESENSOR: Erro GPIO {self.gpio}: {e}") + try: + GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) + GPIO.remove_event_detect(int(self.gpio)) + GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.impulseDetected, bouncetime=20) + except Exception as e: + print(e) + + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + self.reset() + + @action(key="Fake Impulse", parameters=[]) + async def fakeImpulse(self, **kwargs): + self.impulseDetected("fake") - def count(self, channel): - self.impulses += 1 - self.value = round(self.impulses / self.pulsos_litro, 3) + def impulseDetected(self, channel): + logging.debug("impulse detected on") + self.impulses = self.impulses + 1 + self.liter = round(self.LperI * self.impulses,3) + self.value = self.liter async def run(self): - while self.running: + while self.running is True: + self.log_data(self.value) self.push_update(self.value) await asyncio.sleep(1) -# --- PASSO AUTOMÁTICO (FlowStep) --- + def reset(self): + logging.info(f'reset VolumeSensor from {self.liter} to 0.') + self.impulses = 0 + self.liter = 0 + self.value = 0 + self.push_update(self.value) + return "Ok" + + def get_state(self): + return dict(value=self.value) + +########################################### + + +@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), + Property.Actor(label="Actor",description="Actor to switch media flow on and off"), + Property.Sensor(label="Sensor"), + Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) + class FlowStep(CBPiStep): - @parameters([ - Property.Sensor(label="Sensor de Fluxo"), - Property.Actor(label="Bomba/Valvula"), - Property.Number(label="Volume Alvo (L)", configurable=True, default_value=5.0) - ]) - def __init__(self, cbpi, id, props): - super().__init__(cbpi, id, props) - self.target = float(self.props.get("Volume Alvo (L)", 5.0)) - self.sensor_id = self.props.get("Sensor de Fluxo") - self.actor_id = self.props.get("Bomba/Valvula") + + async def on_timer_done(self,timer): + self.summary = "" + self.cbpi.notify(self.name, 'Step finished. Transferred {} {}.'.format(round(self.current_volume,2),self.unit), NotificationType.SUCCESS) + if self.resetsensor == "Yes": + self.sensor.instance.reset() + + if self.actor is not None: + await self.actor_off(self.actor) + await self.next() + + async def on_timer_update(self,timer, seconds): + await self.push_update() + + async def on_start(self): + self.unit = self.cbpi.config.get("flowunit", "L") + self.actor = self.props.get("Actor", None) + self.target_volume = float(self.props.get("Volume",0)) + self.flowsensor = self.props.get("Sensor",None) + logging.info(self.flowsensor) + self.sensor = self.get_sensor(self.flowsensor) + logging.info(self.sensor) + self.resetsensor = self.props.get("Reset","Yes") + + self.sensor.instance.reset() + if self.timer is None: + self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) + + async def on_stop(self): + if self.timer is not None: + await self.timer.stop() + self.summary = "" + if self.actor is not None: + await self.actor_off(self.actor) + await self.push_update() + + async def reset(self): + self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) + if self.actor is not None: + await self.actor_off(self.actor) + if self.resetsensor == "Yes": + self.sensor.instance.reset() + async def run(self): - # Liga a bomba se houver uma configurada - if self.actor_id: - await self.actor_on(self.actor_id) - - while self.running: - try: - # Lê o valor atual do sensor selecionado - current_vol = float(self.get_sensor_value(self.sensor_id).get("value", 0)) - - # Verifica se atingiu o alvo - if current_vol >= self.target: - if self.actor_id: - await self.actor_off(self.actor_id) - self.cbpi.notify("FlowStep", f"Transferencia de {current_vol}L concluida!", type=NotificationType.SUCCESS) - break - except Exception as e: - logger.error(f"FLOWSTEP Error: {e}") - - await asyncio.sleep(0.5) + if self.actor is not None: + await self.actor_on(self.actor) + self.summary="" + await self.push_update() + while self.running == True: + self.current_volume = self.get_sensor_value(self.flowsensor).get("value") + self.summary="Volume: {}".format(self.current_volume) + await self.push_update() + + if self.current_volume >= self.target_volume and self.timer.is_running is not True: + self.timer.start() + self.timer.is_running = True + + await asyncio.sleep(0.2) + + return StepResult.DONE + -# --- REGISTRO DOS PLUGINS --- def setup(cbpi): - cbpi.plugin.register("FlowSensor", FlowSensor) - cbpi.plugin.register("VolumeSensor", VolumeSensor) cbpi.plugin.register("FlowStep", FlowStep) + cbpi.plugin.register("VolumeSensor", VolumeSensor) + cbpi.plugin.register("FlowSensor", FlowSensor) + cbpi.plugin.register("Flowmeter_Config", Flowmeter_Config) + if str(cbpi.static_config.get("mqtt", False)).lower() == "true": + cbpi.plugin.register("MQTTFLowSensor", MQTTFlowSensor) + pass From f009b8488b39ee1003ad3b3cfaa7597cbaf729a8 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:00:09 -0300 Subject: [PATCH 12/36] Atualizado para Raspberry5 --- cbpi4-Flowmeter/__init__.py | 492 ++++++++++-------------------------- 1 file changed, 138 insertions(+), 354 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index bf44a45..4ea5ceb 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,400 +1,184 @@ - # -*- coding: utf-8 -*- -import os -from aiohttp import web import logging -from unittest.mock import MagicMock, patch import asyncio -import random -from cbpi.api import * import time -from cbpi.api.base import CBPiBase -from cbpi.api import parameters, Property, action -from cbpi.api.step import StepResult, CBPiStep -from cbpi.api.timer import Timer -from voluptuous.schema_builder import message -from cbpi.api.dataclasses import NotificationAction, NotificationType -from cbpi.api.dataclasses import Sensor, Kettle, Props -import logging -from socket import timeout -from typing import KeysView +from cbpi.api import * from cbpi.api.config import ConfigType -import json +# Configuração de Log logger = logging.getLogger(__name__) +# Tenta carregar a biblioteca gpiozero (Padrão no RPi 5) try: - import RPi.GPIO as GPIO - mode = GPIO.getmode() - if (mode == None): - GPIO.setmode(GPIO.BCM) - -except Exception as e: - print(e) - pass - -class Flowmeter_Config(CBPiExtension): - - def __init__(self,cbpi): - self.cbpi = cbpi - self._task = asyncio.create_task(self.init_sensor()) - - async def init_sensor(self): - plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") - self.version=plugin[0].get("Version","0.0.0") - self.name=plugin[0].get("Name","cbpi4-Flowmeter") - - self.flowmeter_update = self.cbpi.config.get(self.name+"_update", None) - - unit = self.cbpi.config.get("flowunit", None) - if unit is None: - logging.info("INIT FLOW SENSOR CONFIG") - try: - await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", - source=self.name, - options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) - - except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - else: - if self.flowmeter_update == None or self.flowmeter_update != self.version: - try: - await self.cbpi.config.add("flowunit", unit , type=ConfigType.SELECT, description="Flowmeter unit", - source=self.name, - options=[{"label": "L", "value": "L"}, - {"label": "gal(us)", "value": "gal(us)"}, - {"label": "gal(uk)", "value": "gal(uk)"}, - {"label": "qt", "value": "qt"}]) - - except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - - if self.flowmeter_update == None or self.flowmeter_update != self.version: - try: - await self.cbpi.config.add(self.name+"_update", self.version, type=ConfigType.STRING, - description="Flowmeter Plugin Version", - source='hidden') - except Exception as e: - logger.warning('Unable to update config') - logger.warning(e) - pass - + from gpiozero import DigitalInputDevice + GPIO_AVAILABLE = True +except ImportError: + logger.error("FLOWMETER: Biblioteca 'gpiozero' não encontrada. O plugin rodará em modo simulado.") + GPIO_AVAILABLE = False +# --- CLASSE DE DADOS (Matemática) --- class FlowMeterData(): - SECONDS_IN_A_MINUTE = 60 - MS_IN_A_SECOND = 1000.0 - enabled = True - clicks = 0 - lastClick = 0 - clickDelta = 0 - hertz = 0.0 - flow = 0 # in Liters per second - pour = 0.0 # in Liters - def __init__(self): self.clicks = 0 - self.lastClick = int(time.time() * FlowMeterData.MS_IN_A_SECOND) - self.clickDelta = 0 - self.hertz = 0.0 + self.last_click = int(time.time() * 1000) self.flow = 0.0 self.pour = 0.0 - self.enabled = True - def update(self, currentTime, hertzProp): - #print hertzProp + def update(self, hertz_prop): + current_time = int(time.time() * 1000) self.clicks += 1 - # get the time delta - self.clickDelta = max((currentTime - self.lastClick), 1) - # calculate the instantaneous speed - if self.enabled is True and self.clickDelta < 1000: - self.hertz = FlowMeterData.MS_IN_A_SECOND / self.clickDelta - self.flow = self.hertz / (FlowMeterData.SECONDS_IN_A_MINUTE * hertzProp) # In Liters per second - instPour = self.flow * (self.clickDelta / FlowMeterData.MS_IN_A_SECOND) - self.pour += instPour - # Update the last click - self.lastClick = currentTime - - def clear(self): - self.pour = 0 - return str(self.pour) - - -@parameters([Property.Text(label="Topic", configurable=True, description="MQTT FlowSensor Topic"), - Property.Text(label="PayloadDictionary", configurable=True, default_value="", - description="Where to find msg in payload, leave blank for raw payload"), - Property.Text(label="ResetTopic", configurable=True, description="MQTT FlowSensor Reset Topic")]) -class MQTTFlowSensor(CBPiSensor): + + # Calcula delta em ms + delta = max((current_time - self.last_click), 1) + + # Filtro de ruído: ignora pulsos absurdamente rápidos (<1ms) + if delta < 1000: + # Frequência (Hz) = 1000ms / delta_ms + hertz = 1000.0 / delta + + # Vazão (L/s) = Frequência / Fator K / 60 + self.flow = hertz / (60.0 * hertz_prop) + + # Volume (L) = Vazão * (tempo_do_pulso / 1000) + self.pour += self.flow * (delta / 1000.0) + + self.last_click = current_time - def __init__(self, cbpi, id, props): - super(MQTTFlowSensor, self).__init__(cbpi, id, props) - self.Topic = self.props.get("Topic", None) - self.payload_text = self.props.get("PayloadDictionary", None) - if self.payload_text != None: - self.payload_text = self.payload_text.split('.') - self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message) - self.value: float = 999 - self.ResetTopic = self.props.get("ResetTopic", None) - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - await self.reset() - print("RESET FLOWSENSOR") - - async def on_message(self, message): - val = json.loads(message) - try: - if self.payload_text is not None: - for key in self.payload_text: - val = val.get(key, None) - - if isinstance(val, (int, float, str)): - self.value = float(val) - self.log_data(self.value) - self.push_update(self.value) - except Exception as e: - logging.info("MQTT Sensor Error {}".format(e)) - - async def run(self): - while self.running: - await asyncio.sleep(1) - - async def reset(self): - logging.info("Reset MQTTFlowsensor") - logging.info(self.ResetTopic) - await self.cbpi.satellite.publish(self.ResetTopic, json.dumps({}), True) - return "Ok" - - def get_state(self): - return dict(value=self.value) - - async def on_stop(self): - if self.mqtt_task.done() is False: - self.mqtt_task.cancel() - try: - await self.mqtt_task - except asyncio.CancelledError: - pass - -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), - Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) + def reset(self): + self.pour = 0.0 + self.flow = 0.0 + self.clicks = 0 +# --- SENSOR PRINCIPAL (FlowSensor) --- +@parameters([ + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="Pino BCM"), + Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="Modo de visualização"), + Property.Number(label="Fator K", configurable=True, default_value=7.5, description="Calibração (Hz para 1 L/min)") +]) class FlowSensor(CBPiSensor): - def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) - self.value = 0 - self.fms = dict() - self.gpio=self.props.get("GPIO",0) - self.sensorShow=self.props.get("Display","Total Volume") - self.hertzProp=self.props.get("Hertz", 7.5) - - try: - GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) - GPIO.remove_event_detect(int(self.gpio)) - GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.doAClick, bouncetime=20) - self.fms[int(self.gpio)] = FlowMeterData() - except Exception as e: - print(e) - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - self.reset() - print("RESET FLOWSENSOR") - - - def get_unit(self): - unit = self.cbpi.config.get("flowunit", "L") - if self.sensorShow == "Flow, unit/s": - unit = unit + "/s" - return unit + self.value = 0.0 + self.gpio = int(self.props.get("GPIO", 0)) + self.display_mode = self.props.get("Exibição", "Volume Total") + self.k_factor = float(self.props.get("Fator K", 7.5)) + + # Inicializa lógica matemática + self.data = FlowMeterData() + self.sensor_device = None + + # Configuração do Hardware (GPIOZERO) + if GPIO_AVAILABLE: + try: + # DigitalInputDevice lida com pull_up=True por padrão para sensores tipo switch/hall + # bounce_time=0.02 é o equivalente a bouncetime=20 do RPi.GPIO + self.sensor_device = DigitalInputDevice(self.gpio, pull_up=True, bounce_time=0.02) + + # Define o callback. + # when_activated corresponde a borda de descida (Falling) no pull-up + # when_deactivated corresponde a borda de subida (Rising) no pull-up + # Para contagem de fluxo, qualquer um serve, desde que consistente. + self.sensor_device.when_deactivated = self.pulse_callback + + logger.info(f"FLOWMETER: GPIO {self.gpio} iniciada com sucesso (backend gpiozero).") + except Exception as e: + logger.error(f"FLOWMETER: Falha ao iniciar GPIO {self.gpio}: {e}") - def doAClick(self, channel): - currentTime = int(time.time() * FlowMeterData.MS_IN_A_SECOND) - hertzProp = self.hertzProp - self.fms[int(self.gpio)].update(currentTime, float(hertzProp)) + # Callback executado a cada pulso + def pulse_callback(self): + self.data.update(self.k_factor) - def convert(self, inputFlow): - unit = self.cbpi.config.get("flowunit", "L") - if unit == "gal(us)": - inputFlow = inputFlow * 0.264172052 - elif unit == "gal(uk)": - inputFlow = inputFlow * 0.219969157 - elif unit == "qt": - inputFlow = inputFlow * 1.056688 - else: - pass - if self.sensorShow == "Flow, unit/s": - inputFlow = "{0:.2f}".format(inputFlow) - else: - inputFlow = "{0:.2f}".format(inputFlow) - return inputFlow + # Ação do botão de reset na interface + @action(key="Zerar Volume", parameters=[]) + async def reset_volume(self, **kwargs): + self.data.reset() + self.value = 0.0 + self.push_update(0.0) + # Loop principal de atualização da interface async def run(self): - while self.running is True: - if self.sensorShow == "Total volume": - flow = self.fms[int(self.gpio)].pour - flowConverted = self.convert(flow) - self.value= float(flowConverted) - elif self.sensorShow == "Flow, unit/s": - flow = self.fms[int(self.gpio)].flow - flowConverted = self.convert(flow) - self.value = float(flowConverted) + while self.running: + if self.display_mode == "Volume Total": + val = self.data.pour else: - logging.info("FlowSensor error") - + val = self.data.flow + + self.value = round(val, 2) self.push_update(self.value) await asyncio.sleep(1) - - def getValue(self): - flow = self.fms[int(self.gpio)].pour - flowConverted = self.convert(flow) - return flowConverted - - def reset(self): - logging.info("Reset Flowsensor") - self.fms[int(self.gpio)].clear() - return "Ok" - - def get_state(self): - return dict(value=self.value) - -########## - -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) - + + # Cleanup ao desligar o plugin + def stop(self): + if self.sensor_device: + self.sensor_device.close() + +# --- SENSOR DE VOLUME SIMPLES (Opcional) --- +@parameters([ + Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), + Property.Number(label="Pulsos por Litro", configurable=True, default_value=450) +]) class VolumeSensor(CBPiSensor): - def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) - self.value = 0 + self.value = 0.0 self.impulses = 0 - self.liter = 0 - self.gpio = self.props.get("GPIO",0) - self.IperL = self.props.get("impulsesPerVolumeUnit", 450) - self.LperI = float(1.0) / float(self.IperL) - - try: - GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) - GPIO.remove_event_detect(int(self.gpio)) - GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.impulseDetected, bouncetime=20) - except Exception as e: - print(e) - - @action(key="Reset Sensor", parameters=[]) - async def Reset(self, **kwargs): - self.reset() + self.gpio = int(self.props.get("GPIO", 0)) + self.pulsos_litro = float(self.props.get("Pulsos por Litro", 450)) + self.sensor_device = None - @action(key="Fake Impulse", parameters=[]) - async def fakeImpulse(self, **kwargs): - self.impulseDetected("fake") + if GPIO_AVAILABLE: + try: + self.sensor_device = DigitalInputDevice(self.gpio, pull_up=True, bounce_time=0.02) + self.sensor_device.when_deactivated = self.count + except Exception as e: + logger.error(f"VOLUMESENSOR: Erro GPIO {self.gpio}: {e}") - def impulseDetected(self, channel): - logging.debug("impulse detected on") - self.impulses = self.impulses + 1 - self.liter = round(self.LperI * self.impulses,3) - self.value = self.liter + def count(self): + self.impulses += 1 + self.value = round(self.impulses / self.pulsos_litro, 3) async def run(self): - while self.running is True: - self.log_data(self.value) + while self.running: self.push_update(self.value) await asyncio.sleep(1) + + def stop(self): + if self.sensor_device: + self.sensor_device.close() - def reset(self): - logging.info(f'reset VolumeSensor from {self.liter} to 0.') - self.impulses = 0 - self.liter = 0 - self.value = 0 - self.push_update(self.value) - return "Ok" - - def get_state(self): - return dict(value=self.value) - -########################################### - - -@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), - Property.Actor(label="Actor",description="Actor to switch media flow on and off"), - Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) - +# --- PASSO AUTOMÁTICO (FlowStep) --- class FlowStep(CBPiStep): - - async def on_timer_done(self,timer): - self.summary = "" - self.cbpi.notify(self.name, 'Step finished. Transferred {} {}.'.format(round(self.current_volume,2),self.unit), NotificationType.SUCCESS) - if self.resetsensor == "Yes": - self.sensor.instance.reset() - - if self.actor is not None: - await self.actor_off(self.actor) - await self.next() - - async def on_timer_update(self,timer, seconds): - await self.push_update() - - async def on_start(self): - self.unit = self.cbpi.config.get("flowunit", "L") - self.actor = self.props.get("Actor", None) - self.target_volume = float(self.props.get("Volume",0)) - self.flowsensor = self.props.get("Sensor",None) - logging.info(self.flowsensor) - self.sensor = self.get_sensor(self.flowsensor) - logging.info(self.sensor) - self.resetsensor = self.props.get("Reset","Yes") - - self.sensor.instance.reset() - if self.timer is None: - self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) - - async def on_stop(self): - if self.timer is not None: - await self.timer.stop() - self.summary = "" - if self.actor is not None: - await self.actor_off(self.actor) - await self.push_update() - - async def reset(self): - self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) - if self.actor is not None: - await self.actor_off(self.actor) - if self.resetsensor == "Yes": - self.sensor.instance.reset() - + @parameters([ + Property.Sensor(label="Sensor de Fluxo"), + Property.Actor(label="Bomba/Valvula"), + Property.Number(label="Volume Alvo (L)", configurable=True, default_value=5.0) + ]) + def __init__(self, cbpi, id, props): + super().__init__(cbpi, id, props) + self.target = float(self.props.get("Volume Alvo (L)", 5.0)) + self.sensor_id = self.props.get("Sensor de Fluxo") + self.actor_id = self.props.get("Bomba/Valvula") async def run(self): - if self.actor is not None: - await self.actor_on(self.actor) - self.summary="" - await self.push_update() - while self.running == True: - self.current_volume = self.get_sensor_value(self.flowsensor).get("value") - self.summary="Volume: {}".format(self.current_volume) - await self.push_update() - - if self.current_volume >= self.target_volume and self.timer.is_running is not True: - self.timer.start() - self.timer.is_running = True - - await asyncio.sleep(0.2) - - return StepResult.DONE - + if self.actor_id: + await self.actor_on(self.actor_id) + + while self.running: + try: + current_vol = float(self.get_sensor_value(self.sensor_id).get("value", 0)) + + if current_vol >= self.target: + if self.actor_id: + await self.actor_off(self.actor_id) + self.cbpi.notify("FlowStep", f"Transferencia de {current_vol}L concluida!", type=NotificationType.SUCCESS) + break + except Exception as e: + logger.error(f"FLOWSTEP Error: {e}") + + await asyncio.sleep(0.5) +# --- REGISTRO DOS PLUGINS --- def setup(cbpi): - cbpi.plugin.register("FlowStep", FlowStep) - cbpi.plugin.register("VolumeSensor", VolumeSensor) cbpi.plugin.register("FlowSensor", FlowSensor) - cbpi.plugin.register("Flowmeter_Config", Flowmeter_Config) - if str(cbpi.static_config.get("mqtt", False)).lower() == "true": - cbpi.plugin.register("MQTTFLowSensor", MQTTFlowSensor) - pass + cbpi.plugin.register("VolumeSensor", VolumeSensor) + cbpi.plugin.register("FlowStep", FlowStep) From dfa33184a2215f654314537d4a12af18e26ae409 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:07:25 -0300 Subject: [PATCH 13/36] Reset original --- cbpi4-Flowmeter/__init__.py | 493 ++++++++++++++++++++++++++---------- 1 file changed, 355 insertions(+), 138 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 4ea5ceb..f279694 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,184 +1,401 @@ + + # -*- coding: utf-8 -*- +import os +from aiohttp import web import logging +from unittest.mock import MagicMock, patch import asyncio -import time +import random from cbpi.api import * +import time +from cbpi.api.base import CBPiBase +from cbpi.api import parameters, Property, action +from cbpi.api.step import StepResult, CBPiStep +from cbpi.api.timer import Timer +from voluptuous.schema_builder import message +from cbpi.api.dataclasses import NotificationAction, NotificationType +from cbpi.api.dataclasses import Sensor, Kettle, Props +import logging +from socket import timeout +from typing import KeysView from cbpi.api.config import ConfigType +import json -# Configuração de Log logger = logging.getLogger(__name__) -# Tenta carregar a biblioteca gpiozero (Padrão no RPi 5) try: - from gpiozero import DigitalInputDevice - GPIO_AVAILABLE = True -except ImportError: - logger.error("FLOWMETER: Biblioteca 'gpiozero' não encontrada. O plugin rodará em modo simulado.") - GPIO_AVAILABLE = False + import RPi.GPIO as GPIO + mode = GPIO.getmode() + if (mode == None): + GPIO.setmode(GPIO.BCM) + +except Exception as e: + print(e) + pass + +class Flowmeter_Config(CBPiExtension): + + def __init__(self,cbpi): + self.cbpi = cbpi + self._task = asyncio.create_task(self.init_sensor()) + + async def init_sensor(self): + plugin = await self.cbpi.plugin.load_plugin_list("cbpi4-Flowmeter") + self.version=plugin[0].get("Version","0.0.0") + self.name=plugin[0].get("Name","cbpi4-Flowmeter") + + self.flowmeter_update = self.cbpi.config.get(self.name+"_update", None) + + unit = self.cbpi.config.get("flowunit", None) + if unit is None: + logging.info("INIT FLOW SENSOR CONFIG") + try: + await self.cbpi.config.add("flowunit", "L", type=ConfigType.SELECT, description="Flowmeter unit", + source=self.name, + options=[{"label": "L", "value": "L"}, + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) + + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + else: + if self.flowmeter_update == None or self.flowmeter_update != self.version: + try: + await self.cbpi.config.add("flowunit", unit , type=ConfigType.SELECT, description="Flowmeter unit", + source=self.name, + options=[{"label": "L", "value": "L"}, + {"label": "gal(us)", "value": "gal(us)"}, + {"label": "gal(uk)", "value": "gal(uk)"}, + {"label": "qt", "value": "qt"}]) + + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + + if self.flowmeter_update == None or self.flowmeter_update != self.version: + try: + await self.cbpi.config.add(self.name+"_update", self.version, type=ConfigType.STRING, + description="Flowmeter Plugin Version", + source='hidden') + except Exception as e: + logger.warning('Unable to update config') + logger.warning(e) + pass + -# --- CLASSE DE DADOS (Matemática) --- class FlowMeterData(): + SECONDS_IN_A_MINUTE = 60 + MS_IN_A_SECOND = 1000.0 + enabled = True + clicks = 0 + lastClick = 0 + clickDelta = 0 + hertz = 0.0 + flow = 0 # in Liters per second + pour = 0.0 # in Liters + def __init__(self): self.clicks = 0 - self.last_click = int(time.time() * 1000) + self.lastClick = int(time.time() * FlowMeterData.MS_IN_A_SECOND) + self.clickDelta = 0 + self.hertz = 0.0 self.flow = 0.0 self.pour = 0.0 + self.enabled = True - def update(self, hertz_prop): - current_time = int(time.time() * 1000) + def update(self, currentTime, hertzProp): + #print hertzProp self.clicks += 1 - - # Calcula delta em ms - delta = max((current_time - self.last_click), 1) - - # Filtro de ruído: ignora pulsos absurdamente rápidos (<1ms) - if delta < 1000: - # Frequência (Hz) = 1000ms / delta_ms - hertz = 1000.0 / delta - - # Vazão (L/s) = Frequência / Fator K / 60 - self.flow = hertz / (60.0 * hertz_prop) - - # Volume (L) = Vazão * (tempo_do_pulso / 1000) - self.pour += self.flow * (delta / 1000.0) - - self.last_click = current_time + # get the time delta + self.clickDelta = max((currentTime - self.lastClick), 1) + # calculate the instantaneous speed + if self.enabled is True and self.clickDelta < 1000: + self.hertz = FlowMeterData.MS_IN_A_SECOND / self.clickDelta + self.flow = self.hertz / (FlowMeterData.SECONDS_IN_A_MINUTE * hertzProp) # In Liters per second + instPour = self.flow * (self.clickDelta / FlowMeterData.MS_IN_A_SECOND) + self.pour += instPour + # Update the last click + self.lastClick = currentTime - def reset(self): - self.pour = 0.0 - self.flow = 0.0 - self.clicks = 0 + def clear(self): + self.pour = 0 + return str(self.pour) + + +@parameters([Property.Text(label="Topic", configurable=True, description="MQTT FlowSensor Topic"), + Property.Text(label="PayloadDictionary", configurable=True, default_value="", + description="Where to find msg in payload, leave blank for raw payload"), + Property.Text(label="ResetTopic", configurable=True, description="MQTT FlowSensor Reset Topic")]) +class MQTTFlowSensor(CBPiSensor): + + def __init__(self, cbpi, id, props): + super(MQTTFlowSensor, self).__init__(cbpi, id, props) + self.Topic = self.props.get("Topic", None) + self.payload_text = self.props.get("PayloadDictionary", None) + if self.payload_text != None: + self.payload_text = self.payload_text.split('.') + self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message) + self.value: float = 999 + self.ResetTopic = self.props.get("ResetTopic", None) + + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + await self.reset() + print("RESET FLOWSENSOR") + + async def on_message(self, message): + val = json.loads(message) + try: + if self.payload_text is not None: + for key in self.payload_text: + val = val.get(key, None) + + if isinstance(val, (int, float, str)): + self.value = float(val) + self.log_data(self.value) + self.push_update(self.value) + except Exception as e: + logging.info("MQTT Sensor Error {}".format(e)) + + async def run(self): + while self.running: + await asyncio.sleep(1) + + async def reset(self): + logging.info("Reset MQTTFlowsensor") + logging.info(self.ResetTopic) + await self.cbpi.satellite.publish(self.ResetTopic, json.dumps({}), True) + return "Ok" + + def get_state(self): + return dict(value=self.value) + + async def on_stop(self): + if self.mqtt_task.done() is False: + self.mqtt_task.cancel() + try: + await self.mqtt_task + except asyncio.CancelledError: + pass + +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), + Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) -# --- SENSOR PRINCIPAL (FlowSensor) --- -@parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="Pino BCM"), - Property.Select(label="Exibição", options=["Volume Total", "Fluxo L/s"], description="Modo de visualização"), - Property.Number(label="Fator K", configurable=True, default_value=7.5, description="Calibração (Hz para 1 L/min)") -]) class FlowSensor(CBPiSensor): + def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) - self.value = 0.0 - self.gpio = int(self.props.get("GPIO", 0)) - self.display_mode = self.props.get("Exibição", "Volume Total") - self.k_factor = float(self.props.get("Fator K", 7.5)) - - # Inicializa lógica matemática - self.data = FlowMeterData() - self.sensor_device = None - - # Configuração do Hardware (GPIOZERO) - if GPIO_AVAILABLE: - try: - # DigitalInputDevice lida com pull_up=True por padrão para sensores tipo switch/hall - # bounce_time=0.02 é o equivalente a bouncetime=20 do RPi.GPIO - self.sensor_device = DigitalInputDevice(self.gpio, pull_up=True, bounce_time=0.02) - - # Define o callback. - # when_activated corresponde a borda de descida (Falling) no pull-up - # when_deactivated corresponde a borda de subida (Rising) no pull-up - # Para contagem de fluxo, qualquer um serve, desde que consistente. - self.sensor_device.when_deactivated = self.pulse_callback - - logger.info(f"FLOWMETER: GPIO {self.gpio} iniciada com sucesso (backend gpiozero).") - except Exception as e: - logger.error(f"FLOWMETER: Falha ao iniciar GPIO {self.gpio}: {e}") + self.value = 0 + self.fms = dict() + self.gpio=self.props.get("GPIO",0) + self.sensorShow=self.props.get("Display","Total Volume") + self.hertzProp=self.props.get("Hertz", 7.5) + + try: + GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) + GPIO.remove_event_detect(int(self.gpio)) + GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.doAClick, bouncetime=20) + self.fms[int(self.gpio)] = FlowMeterData() + except Exception as e: + print(e) + + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + self.reset() + print("RESET FLOWSENSOR") + - # Callback executado a cada pulso - def pulse_callback(self): - self.data.update(self.k_factor) + def get_unit(self): + unit = self.cbpi.config.get("flowunit", "L") + if self.sensorShow == "Flow, unit/s": + unit = unit + "/s" + return unit - # Ação do botão de reset na interface - @action(key="Zerar Volume", parameters=[]) - async def reset_volume(self, **kwargs): - self.data.reset() - self.value = 0.0 - self.push_update(0.0) + def doAClick(self, channel): + currentTime = int(time.time() * FlowMeterData.MS_IN_A_SECOND) + hertzProp = self.hertzProp + self.fms[int(self.gpio)].update(currentTime, float(hertzProp)) + + def convert(self, inputFlow): + unit = self.cbpi.config.get("flowunit", "L") + if unit == "gal(us)": + inputFlow = inputFlow * 0.264172052 + elif unit == "gal(uk)": + inputFlow = inputFlow * 0.219969157 + elif unit == "qt": + inputFlow = inputFlow * 1.056688 + else: + pass + if self.sensorShow == "Flow, unit/s": + inputFlow = "{0:.2f}".format(inputFlow) + else: + inputFlow = "{0:.2f}".format(inputFlow) + return inputFlow - # Loop principal de atualização da interface async def run(self): - while self.running: - if self.display_mode == "Volume Total": - val = self.data.pour + while self.running is True: + if self.sensorShow == "Total volume": + flow = self.fms[int(self.gpio)].pour + flowConverted = self.convert(flow) + self.value= float(flowConverted) + elif self.sensorShow == "Flow, unit/s": + flow = self.fms[int(self.gpio)].flow + flowConverted = self.convert(flow) + self.value = float(flowConverted) else: - val = self.data.flow - - self.value = round(val, 2) + logging.info("FlowSensor error") + self.push_update(self.value) await asyncio.sleep(1) - - # Cleanup ao desligar o plugin - def stop(self): - if self.sensor_device: - self.sensor_device.close() - -# --- SENSOR DE VOLUME SIMPLES (Opcional) --- -@parameters([ - Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), - Property.Number(label="Pulsos por Litro", configurable=True, default_value=450) -]) + + def getValue(self): + flow = self.fms[int(self.gpio)].pour + flowConverted = self.convert(flow) + return flowConverted + + def reset(self): + logging.info("Reset Flowsensor") + self.fms[int(self.gpio)].clear() + return "Ok" + + def get_state(self): + return dict(value=self.value) + +########## + +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) + class VolumeSensor(CBPiSensor): + def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) - self.value = 0.0 + self.value = 0 self.impulses = 0 - self.gpio = int(self.props.get("GPIO", 0)) - self.pulsos_litro = float(self.props.get("Pulsos por Litro", 450)) - self.sensor_device = None + self.liter = 0 + self.gpio = self.props.get("GPIO",0) + self.IperL = self.props.get("impulsesPerVolumeUnit", 450) + self.LperI = float(1.0) / float(self.IperL) - if GPIO_AVAILABLE: - try: - self.sensor_device = DigitalInputDevice(self.gpio, pull_up=True, bounce_time=0.02) - self.sensor_device.when_deactivated = self.count - except Exception as e: - logger.error(f"VOLUMESENSOR: Erro GPIO {self.gpio}: {e}") + try: + GPIO.setup(int(self.gpio),GPIO.IN, pull_up_down = GPIO.PUD_UP) + GPIO.remove_event_detect(int(self.gpio)) + GPIO.add_event_detect(int(self.gpio), GPIO.RISING, callback=self.impulseDetected, bouncetime=20) + except Exception as e: + print(e) + + @action(key="Reset Sensor", parameters=[]) + async def Reset(self, **kwargs): + self.reset() + + @action(key="Fake Impulse", parameters=[]) + async def fakeImpulse(self, **kwargs): + self.impulseDetected("fake") - def count(self): - self.impulses += 1 - self.value = round(self.impulses / self.pulsos_litro, 3) + def impulseDetected(self, channel): + logging.debug("impulse detected on") + self.impulses = self.impulses + 1 + self.liter = round(self.LperI * self.impulses,3) + self.value = self.liter async def run(self): - while self.running: + while self.running is True: + self.log_data(self.value) self.push_update(self.value) await asyncio.sleep(1) - - def stop(self): - if self.sensor_device: - self.sensor_device.close() -# --- PASSO AUTOMÁTICO (FlowStep) --- + def reset(self): + logging.info(f'reset VolumeSensor from {self.liter} to 0.') + self.impulses = 0 + self.liter = 0 + self.value = 0 + self.push_update(self.value) + return "Ok" + + def get_state(self): + return dict(value=self.value) + +########################################### + + +@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), + Property.Actor(label="Actor",description="Actor to switch media flow on and off"), + Property.Sensor(label="Sensor"), + Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) + class FlowStep(CBPiStep): - @parameters([ - Property.Sensor(label="Sensor de Fluxo"), - Property.Actor(label="Bomba/Valvula"), - Property.Number(label="Volume Alvo (L)", configurable=True, default_value=5.0) - ]) - def __init__(self, cbpi, id, props): - super().__init__(cbpi, id, props) - self.target = float(self.props.get("Volume Alvo (L)", 5.0)) - self.sensor_id = self.props.get("Sensor de Fluxo") - self.actor_id = self.props.get("Bomba/Valvula") + + async def on_timer_done(self,timer): + self.summary = "" + self.cbpi.notify(self.name, 'Step finished. Transferred {} {}.'.format(round(self.current_volume,2),self.unit), NotificationType.SUCCESS) + if self.resetsensor == "Yes": + self.sensor.instance.reset() + + if self.actor is not None: + await self.actor_off(self.actor) + await self.next() + + async def on_timer_update(self,timer, seconds): + await self.push_update() + + async def on_start(self): + self.unit = self.cbpi.config.get("flowunit", "L") + self.actor = self.props.get("Actor", None) + self.target_volume = float(self.props.get("Volume",0)) + self.flowsensor = self.props.get("Sensor",None) + logging.info(self.flowsensor) + self.sensor = self.get_sensor(self.flowsensor) + logging.info(self.sensor) + self.resetsensor = self.props.get("Reset","Yes") + + self.sensor.instance.reset() + if self.timer is None: + self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) + + async def on_stop(self): + if self.timer is not None: + await self.timer.stop() + self.summary = "" + if self.actor is not None: + await self.actor_off(self.actor) + await self.push_update() + + async def reset(self): + self.timer = Timer(1,on_update=self.on_timer_update, on_done=self.on_timer_done) + if self.actor is not None: + await self.actor_off(self.actor) + if self.resetsensor == "Yes": + self.sensor.instance.reset() + async def run(self): - if self.actor_id: - await self.actor_on(self.actor_id) - - while self.running: - try: - current_vol = float(self.get_sensor_value(self.sensor_id).get("value", 0)) - - if current_vol >= self.target: - if self.actor_id: - await self.actor_off(self.actor_id) - self.cbpi.notify("FlowStep", f"Transferencia de {current_vol}L concluida!", type=NotificationType.SUCCESS) - break - except Exception as e: - logger.error(f"FLOWSTEP Error: {e}") - - await asyncio.sleep(0.5) + if self.actor is not None: + await self.actor_on(self.actor) + self.summary="" + await self.push_update() + while self.running == True: + self.current_volume = self.get_sensor_value(self.flowsensor).get("value") + self.summary="Volume: {}".format(self.current_volume) + await self.push_update() + + if self.current_volume >= self.target_volume and self.timer.is_running is not True: + self.timer.start() + self.timer.is_running = True + + await asyncio.sleep(0.2) + + return StepResult.DONE + -# --- REGISTRO DOS PLUGINS --- def setup(cbpi): - cbpi.plugin.register("FlowSensor", FlowSensor) - cbpi.plugin.register("VolumeSensor", VolumeSensor) cbpi.plugin.register("FlowStep", FlowStep) + cbpi.plugin.register("VolumeSensor", VolumeSensor) + cbpi.plugin.register("FlowSensor", FlowSensor) + cbpi.plugin.register("Flowmeter_Config", Flowmeter_Config) + if str(cbpi.static_config.get("mqtt", False)).lower() == "true": + cbpi.plugin.register("MQTTFLowSensor", MQTTFlowSensor) + pass From 777e8af6ebf4790398eac93f9ac28810a540cef9 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:04:44 -0300 Subject: [PATCH 14/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20autor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 8d1d545..bc52e23 100644 --- a/setup.py +++ b/setup.py @@ -7,11 +7,11 @@ long_description = f.read() setup(name='cbpi4-Flowmeter', - version='0.0.6', - description='CraftBeerPi4 Flowsensor / Step Plugin ', - author='Alexander Vollkopf', - author_email='avollkopf@web.de', - url='https://github.com/PiBrewing/cbpi4-Flowmeter', + version='0.0.7', + description='Sensor de fluxo e volume CBPI4 (YF-201)', + author='Edgar Louzano', + author_email='edgar.verdile@gmail.com', + url='https://github.com/louzano/cbpi4-Flowmeter', include_package_data=True, keywords='globalsettings', package_data={ @@ -24,4 +24,4 @@ ], long_description=long_description, long_description_content_type='text/markdown' - ) \ No newline at end of file + ) From 11469f6843b88ed4620b0731064bee2933415ce5 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:20:44 -0300 Subject: [PATCH 15/36] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20leia-me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 89 +++++++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 881cc7c..409ff01 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,65 @@ -# CraftBeerPi4 FLowmeter Sensor / Step Plugin +# Sensor de fluxo e volume CBPI4 -*For recently added VolumeSensor functionality scroll down* +Este plugin foi portado da versão CBPI3 para o CBPI4 (https://github.com/nanab/Flowmeter) -This plugin has been ported from the craftbeerpi3 plugin version (https://github.com/nanab/Flowmeter) +O plugin inclui um sensor com ação para reiniciá-lo e uma etapa personalizada. +Use um resistor de 10k ohms no pino de sinal do sensor para proteger seu Raspberry Pi ou conecte o sensor de fluxo tipo Hall (YF-201 é recomendado) à sua placa de expansão Craftbeerpi nas portas do medidor de fluxo. -The plugin includes sensor with action to reset the sensor and a custom step. -Use a 10k ohm resistor on sensors signal pin to protect your Pi or connect the hall type flow sensor to your Craftbeerpi extension board at the flowmeter ports. +Conecte o sensor ao Raspberry Pi: +Vermelho -> 5V. +Preto -> GND. +Amarelo -> resistor de 10k ohms -> pino GPIO. (ou dados na placa de expansão. Nenhum resistor extra é necessário aqui) -Wire the sensor to the pi: -Red -> 5v. -Black -> GND. -Yellow -> 10k ohm resistor -> GPIO pin. (or data on the extension board. No extra resistor required here) +- Instalação testada no CBPI 4 V4.7.1 - Codename: Winter Bock || GUIversion: 0.5.0 com SO Trixie +- Instalação: pipx runpip cbpi4 uninstall cbpi4-flowmeter -- Installation: - - pypi release: sudo pip3 install cbpi4-Flowmeter - - PiBrewing release: sudo pip3 install https://github.com/avollkopf/cbpi4-Flowmeter/archive/main.zip - - prash3r VolumeSensor testing branch: sudo pip3 install https://github.com/prash3r/cbpi4-Flowmeter/archive/VolumeSensor.zip - -- Sensor Usage: - - On the settings page, choose a unit for the Volume (e.g. L, qt, gal, ...) - - Add Sensor under Hardware and choose Flowmeter as Type - - Several parameters can be set: - - GPIO defines the GPIO that is used for the signal of the sensor (connected to the yellow cable) - - Display defines if the total volume or the flow per second is displayed - - Hertz: Here you need to set the frequency of your sensor (Singals per Liter per minute). This should be documented in the sensor datasheet +- Uso do sensor: +- Na página de configurações, escolha uma unidade para o volume (por exemplo, L, qt, gal, ...) +- Adicione o sensor em Hardware e escolha Fluxômetro como Tipo +- Vários parâmetros podem ser configurados: +- GPIO: define o GPIO usado para o sinal do sensor (conectado ao cabo amarelo) +- Display: define se o volume total ou a vazão por segundo será exibido +- Hertz: Aqui você precisa definir a frequência do seu sensor (Sinais por segundo). Isso deve estar documentado na folha de dados do sensor, como base use 7.5. ![Flowsensor Settings](https://github.com/avollkopf/cbpi4-Flowmeter/blob/main/SensorConfig.png?raw=true) - -- Once configured, you need to add the sensor to the Dashboard. -- Please select Yes for Action as this will add an additional menu on the right side of the sensor to reset the sensor to 0 +- Após a configuração, você precisa adicionar o sensor ao Painel. +- Selecione "Sim" para a ação, pois isso adicionará um menu adicional no lado direito do sensor para redefini-lo para 0. ![Flowsensor Action Setting](https://github.com/avollkopf/cbpi4-Flowmeter/blob/main/SensorActionSetting.png?raw=true) ![Flowsensor Action Button](https://github.com/avollkopf/cbpi4-Flowmeter/blob/main/SensorActionButton.png?raw=true) -- When you press the menu button on the right side of the sensor, a menu wil show up where you can reset the sensor. - +- Ao pressionar o botão de menu no lado direito do sensor, um menu será exibido, onde você poderá redefinir o sensor. +- ![Flowsensor Action Menu](https://github.com/avollkopf/cbpi4-Flowmeter/blob/main/SensorAction.png?raw=true) -- Flowstep Usage: - - The plugin provides a step where you can define a volume that should flow while the step is active. - - You need to select your flowsensor as sensor. - - An actor has to be defined that triggers the start and stop of the flow (e.g. magnetic valve) - - You need to enter the volume that should flow while the step is active - - When the step starts, the sensor will be set to 0. - - You can select if the sensor should be set to 0 once the step is completed. - +Utilização do Flowstep: + - O plugin fornece uma etapa onde você pode definir um volume que deve fluir enquanto a etapa estiver ativa. + - Você precisa selecionar seu sensor de fluxo como sensor. + - Um atuador deve ser definido para acionar o início e a parada da etapa (por exemplo, uma válvula magnética). + - Você precisa inserir o volume que deve fluir enquanto a etapa estiver ativo. + - Quando a etapa começar, o sensor será definido como 0. + - Você pode selecionar se o sensor deve ser definido como 0 após a conclusão da etapa. + ![Flowstep](https://github.com/avollkopf/cbpi4-Flowmeter/blob/main/FlowStep.png?raw=true) -## VolumeSensor functionality - -The recently added *very simple* VolumeSensor functionality can be used like this: - -Parameters: - - GPIO: The GPIO Pin number in BCM numbering - - impulsesPerVolumeUnit: the amount of impulses that should be displaying the volume of 1 of whatever Unit. This is unit agnostic. Just use the same unit in your FlowStep if you use it. - -The VolumeSensor does nothing more then to count impulses and calculate the volume the number of impulses represent. - -Actions: - - Reset Sensor: resets the countet impulses and volume to 0 - - Fake Impulse: fakes the detection of an impulse (i used this for testing because i dont have a flow sensor) - +## Funcionalidades do sensor de fluxo +A funcionalidade *muito simples* do Sensor de Volume, adicionada recentemente, pode ser usada da seguinte forma: +Parâmetros: + - GPIO: O número do pino GPIO na numeração BCM + - impulsesPerVolumeUnit: a quantidade de impulsos que devem exibir o volume de 1 unidade. Isso é independente da unidade. + - Basta usar a mesma unidade na etapa, se você a utiliza. + - O Sensor de Volume apenas conta os impulsos e calcula o volume que o número de impulsos representa. + +Ações: + - Reset Sensor: redefine a contagem de impulsos e o volume para 0 + - Fake Impulse: simula a detecção de um impulso (usado para teste se não tiver um sensor de fluxo) ## Changelog: +- 21.01.26: (0.0.7) Tradução PT-BR e ajuste para Trixie - 10.06.23: (0.0.6) bump version to release - 14.05.23: (0.0.6.rc1) added simple VolumeSensor and cbpi4 requirement - 14.04.23: (0.0.5.a2) fixed bug in parameter generation From dc8219c28d41c5c7f42835e2b9634b4b6177ff61 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:29:44 -0300 Subject: [PATCH 16/36] =?UTF-8?q?Teste=20trixie=20+=20tradu=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cbpi4-Flowmeter/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index f279694..e276e98 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -25,7 +25,8 @@ logger = logging.getLogger(__name__) try: - import RPi.GPIO as GPIO + #import RPi.GPIO as GPIO + import rpi.lgpio as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) @@ -182,8 +183,8 @@ async def on_stop(self): pass @parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), - Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Define se o volume total ou a vazão volumétrica será exibida."), + Property.Number(label="Hertz", configurable=True, description="Aqui você pode ajustar a frequência do medidor de vazão [Hertz, o padrão é 7,5]. Com esse valor, você pode calibrar o sensor.")]) class FlowSensor(CBPiSensor): @@ -267,8 +268,8 @@ def get_state(self): ########## -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO que é usado pelo sensor de fluxo"), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Aqui você precisa configurar quantos impulsos por unidade de medida o sensor está enviando.")]) class VolumeSensor(CBPiSensor): @@ -322,10 +323,10 @@ def get_state(self): ########################################### -@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), - Property.Actor(label="Actor",description="Actor to switch media flow on and off"), +@parameters([Property.Number(label="Volume", description="Limite de volume para esta etapa", configurable=True), + Property.Actor(label="Actor",description="Atuador para ativar e desativar o fluxo de líquido"), Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) + Property.Select(label="Reset", options=["Yes","No"],description="Reinicie o valor de vazão quando terminar.")]) class FlowStep(CBPiStep): From 14c3a1c1120aee87dddd952a4c8c3ab9573df78d Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:59:44 -0300 Subject: [PATCH 17/36] GPIOZERO --- cbpi4-Flowmeter/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index e276e98..7347359 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,5 +1,3 @@ - - # -*- coding: utf-8 -*- import os from aiohttp import web @@ -26,7 +24,8 @@ try: #import RPi.GPIO as GPIO - import rpi.lgpio as GPIO + #import RPi.LGPIO as GPIO + import GPIOZERO as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From 112be49d146325e0d47cf4992ef73ab5526e39f4 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:06:17 -0300 Subject: [PATCH 18/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 7347359..d3be1f6 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -23,9 +23,9 @@ logger = logging.getLogger(__name__) try: - #import RPi.GPIO as GPIO + import RPi.GPIO as GPIO #import RPi.LGPIO as GPIO - import GPIOZERO as GPIO + #import GPIOZERO as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From dd837288dfebd3cf49706d2f923645fd6dd1cd1e Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:12:17 -0300 Subject: [PATCH 19/36] Update setup.py --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index bc52e23..b4020b2 100644 --- a/setup.py +++ b/setup.py @@ -19,9 +19,7 @@ '': ['*.txt', '*.rst', '*.yaml'], 'cbpi4-Flowmeter': ['*','*.txt', '*.rst', '*.yaml']}, packages=['cbpi4-Flowmeter'], - install_requires=[ - 'cbpi4>=4.1.10.rc2' - ], + install_requires=['cbpi4>=4.1.10.rc2'], long_description=long_description, long_description_content_type='text/markdown' ) From 39514ecba3d2cf9d9b0775e67e9676e2e665dddb Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:49:38 -0300 Subject: [PATCH 20/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index d3be1f6..85432f3 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -23,11 +23,11 @@ logger = logging.getLogger(__name__) try: - import RPi.GPIO as GPIO + #import RPi.GPIO as GPIO #import RPi.LGPIO as GPIO #import GPIOZERO as GPIO - mode = GPIO.getmode() - if (mode == None): + #mode = GPIO.getmode() + #if (mode == None): GPIO.setmode(GPIO.BCM) except Exception as e: @@ -181,7 +181,7 @@ async def on_stop(self): except asyncio.CancelledError: pass -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO que está conectado o sensorr"), Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Define se o volume total ou a vazão volumétrica será exibida."), Property.Number(label="Hertz", configurable=True, description="Aqui você pode ajustar a frequência do medidor de vazão [Hertz, o padrão é 7,5]. Com esse valor, você pode calibrar o sensor.")]) From 2f0f3a15648722634745022f2007412c725ecdaa Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:01:11 -0300 Subject: [PATCH 21/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 85432f3..c535a62 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -28,7 +28,8 @@ #import GPIOZERO as GPIO #mode = GPIO.getmode() #if (mode == None): - GPIO.setmode(GPIO.BCM) + #ED alterado de GPIO para 21 na linha abaixo + GPIO.setmode(21) except Exception as e: print(e) @@ -191,7 +192,8 @@ def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0 self.fms = dict() - self.gpio=self.props.get("GPIO",0) + #Ed Alterado de 0 para 21 na linha abaixo + self.gpio=self.props.get("GPIO",21) self.sensorShow=self.props.get("Display","Total Volume") self.hertzProp=self.props.get("Hertz", 7.5) From dbee6f8bbd0041501e97c0601c5d3d6355a85367 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:17:08 -0300 Subject: [PATCH 22/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index c535a62..f139a1a 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -24,7 +24,7 @@ try: #import RPi.GPIO as GPIO - #import RPi.LGPIO as GPIO + import LGPIO as GPIO #import GPIOZERO as GPIO #mode = GPIO.getmode() #if (mode == None): From 8f7c86a0faedba230ce87b7d194aadbfaa0d601d Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:33:25 -0300 Subject: [PATCH 23/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index f139a1a..c4bfdf5 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -279,7 +279,8 @@ def __init__(self, cbpi, id, props): self.value = 0 self.impulses = 0 self.liter = 0 - self.gpio = self.props.get("GPIO",0) + #Ed alterado 0 para 21 na linha abaixo + self.gpio = self.props.get("GPIO",21) self.IperL = self.props.get("impulsesPerVolumeUnit", 450) self.LperI = float(1.0) / float(self.IperL) From c0f82ce60b602e4ddc93f91c76aa156eded47b5e Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:35:33 -0300 Subject: [PATCH 24/36] Update __init__.py From 807f83fe60a39e1c4535eec80bf917f91f274914 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:50:44 -0300 Subject: [PATCH 25/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index c4bfdf5..2b886c8 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -24,10 +24,10 @@ try: #import RPi.GPIO as GPIO - import LGPIO as GPIO + import rpi-lgpio as GPIO #import GPIOZERO as GPIO - #mode = GPIO.getmode() - #if (mode == None): + mode = GPIO.getmode() + if (mode == None): #ED alterado de GPIO para 21 na linha abaixo GPIO.setmode(21) From eb334cab1ac8eac8688daecbcb3c9b33c736440c Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:18:56 -0300 Subject: [PATCH 26/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 2b886c8..44e9588 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -24,7 +24,7 @@ try: #import RPi.GPIO as GPIO - import rpi-lgpio as GPIO + import rpi.lgpio as GPIO #import GPIOZERO as GPIO mode = GPIO.getmode() if (mode == None): From ead8cf0dbe6a33ae814a2a67f666e334bb375ddc Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:23:16 -0300 Subject: [PATCH 27/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 44e9588..1fd994a 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -25,11 +25,9 @@ try: #import RPi.GPIO as GPIO import rpi.lgpio as GPIO - #import GPIOZERO as GPIO mode = GPIO.getmode() if (mode == None): - #ED alterado de GPIO para 21 na linha abaixo - GPIO.setmode(21) + GPIO.setmode(GPIO.BCM) except Exception as e: print(e) From dd0e97dd282e8636af4cbba30cf6af04af072535 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:34:12 -0300 Subject: [PATCH 28/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 1fd994a..2a52e9c 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -24,7 +24,7 @@ try: #import RPi.GPIO as GPIO - import rpi.lgpio as GPIO + import gpiozero as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From 5e636b3cd144aa6980daa57dbb9eb0057e433a5a Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:39:00 -0300 Subject: [PATCH 29/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 2a52e9c..a8c9606 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -24,7 +24,7 @@ try: #import RPi.GPIO as GPIO - import gpiozero as GPIO + import gpio.zero as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From 6aa724a60581184357a1bf8d932a2bd37177ae79 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:48:30 -0300 Subject: [PATCH 30/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index a8c9606..7df8367 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -23,8 +23,7 @@ logger = logging.getLogger(__name__) try: - #import RPi.GPIO as GPIO - import gpio.zero as GPIO + import RPi.LGPIO as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From edf3293299ee1092d9abbd437848677ed87a4da4 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:54:52 -0300 Subject: [PATCH 31/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 7df8367..b622fcc 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) try: - import RPi.LGPIO as GPIO + import RPi.GPIO as GPIO mode = GPIO.getmode() if (mode == None): GPIO.setmode(GPIO.BCM) From 66a36510dfa6c58cb52f4862a9985d2bcb1ec87b Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:15:16 -0300 Subject: [PATCH 32/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index b622fcc..31a066c 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,3 +1,4 @@ + # -*- coding: utf-8 -*- import os from aiohttp import web @@ -189,8 +190,7 @@ def __init__(self, cbpi, id, props): super(FlowSensor, self).__init__(cbpi, id, props) self.value = 0 self.fms = dict() - #Ed Alterado de 0 para 21 na linha abaixo - self.gpio=self.props.get("GPIO",21) + self.gpio=self.props.get("GPIO",0) self.sensorShow=self.props.get("Display","Total Volume") self.hertzProp=self.props.get("Hertz", 7.5) From 7c56bf5521318c6c8a07cc8a49890f7bac8e2b8d Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:21:57 -0300 Subject: [PATCH 33/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 31a066c..a9e282b 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -275,9 +275,8 @@ def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) self.value = 0 self.impulses = 0 - self.liter = 0 - #Ed alterado 0 para 21 na linha abaixo - self.gpio = self.props.get("GPIO",21) + self.liter = 0 + self.gpio = self.props.get("GPIO",0) self.IperL = self.props.get("impulsesPerVolumeUnit", 450) self.LperI = float(1.0) / float(self.IperL) From 59f4170e5b4330fae37a0f3744f465b4fd4ca4cf Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:25:18 -0300 Subject: [PATCH 34/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index a9e282b..bf44a45 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -27,7 +27,7 @@ import RPi.GPIO as GPIO mode = GPIO.getmode() if (mode == None): - GPIO.setmode(GPIO.BCM) + GPIO.setmode(GPIO.BCM) except Exception as e: print(e) @@ -180,9 +180,9 @@ async def on_stop(self): except asyncio.CancelledError: pass -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO que está conectado o sensorr"), - Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Define se o volume total ou a vazão volumétrica será exibida."), - Property.Number(label="Hertz", configurable=True, description="Aqui você pode ajustar a frequência do medidor de vazão [Hertz, o padrão é 7,5]. Com esse valor, você pode calibrar o sensor.")]) +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Select(label="Display", options=["Total volume", "Flow, unit/s"],description="Defines if total volume or volume flow is displayed"), + Property.Number(label="Hertz", configurable=True, description="Here you can adjust the freequency for the flowmeter [Hertz, default is 7.5]. With this value you can calibrate the sensor.")]) class FlowSensor(CBPiSensor): @@ -266,8 +266,8 @@ def get_state(self): ########## -@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO que é usado pelo sensor de fluxo"), - Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Aqui você precisa configurar quantos impulsos por unidade de medida o sensor está enviando.")]) +@parameters([Property.Select(label="GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27],description="GPIO that is used by the Flowsensor"), + Property.Number(label="impulsesPerVolumeUnit", configurable=True, description="Here you need to configure how many impulses per Unit of measurement the sensor is sending. ")]) class VolumeSensor(CBPiSensor): @@ -275,7 +275,7 @@ def __init__(self, cbpi, id, props): super(VolumeSensor, self).__init__(cbpi, id, props) self.value = 0 self.impulses = 0 - self.liter = 0 + self.liter = 0 self.gpio = self.props.get("GPIO",0) self.IperL = self.props.get("impulsesPerVolumeUnit", 450) self.LperI = float(1.0) / float(self.IperL) @@ -321,10 +321,10 @@ def get_state(self): ########################################### -@parameters([Property.Number(label="Volume", description="Limite de volume para esta etapa", configurable=True), - Property.Actor(label="Actor",description="Atuador para ativar e desativar o fluxo de líquido"), +@parameters([Property.Number(label="Volume", description="Volume limit for this step", configurable=True), + Property.Actor(label="Actor",description="Actor to switch media flow on and off"), Property.Sensor(label="Sensor"), - Property.Select(label="Reset", options=["Yes","No"],description="Reinicie o valor de vazão quando terminar.")]) + Property.Select(label="Reset", options=["Yes","No"],description="Reset Flowmeter when done")]) class FlowStep(CBPiStep): From 1d797856063e396746c1d825819f48b27189a392 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:50:17 -0300 Subject: [PATCH 35/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index bf44a45..45f06c8 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -1,4 +1,3 @@ - # -*- coding: utf-8 -*- import os from aiohttp import web @@ -27,7 +26,7 @@ import RPi.GPIO as GPIO mode = GPIO.getmode() if (mode == None): - GPIO.setmode(GPIO.BCM) + GPIO.setmode(GPIO.BOARD) except Exception as e: print(e) From e4d01826543f8b2d4f342694465f37050ee5ba47 Mon Sep 17 00:00:00 2001 From: Edgar Louzano Verdile <68625105+louzano@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:57:37 -0300 Subject: [PATCH 36/36] Update __init__.py --- cbpi4-Flowmeter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi4-Flowmeter/__init__.py b/cbpi4-Flowmeter/__init__.py index 45f06c8..1500923 100644 --- a/cbpi4-Flowmeter/__init__.py +++ b/cbpi4-Flowmeter/__init__.py @@ -26,7 +26,7 @@ import RPi.GPIO as GPIO mode = GPIO.getmode() if (mode == None): - GPIO.setmode(GPIO.BOARD) + GPIO.setmode(GPIO.BCM) except Exception as e: print(e)