commit 4ca0462d583cedcbfa4809b8159117381b548978 Author: Raymond Bourges Date: Sat May 24 10:30:00 2025 +0200 FEAT : Anémotrètre en modbus -> mqtt OK :-) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23ed2b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +venv/ \ No newline at end of file diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..ce20a0b --- /dev/null +++ b/compose.yml @@ -0,0 +1,14 @@ +version: '3' +services: + esphome: + container_name: esphome + image: ghcr.io/esphome/esphome:2025.4.2 + volumes: + - "${PWD}/config:/config" + - /etc/localtime:/etc/localtime:ro + restart: always + privileged: true + network_mode: host + environment: + - USERNAME=admin + - PASSWORD=admin \ No newline at end of file diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..d8b4157 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,5 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +/secrets.yaml diff --git a/config/archive/seed-modbus.yaml b/config/archive/seed-modbus.yaml new file mode 100644 index 0000000..c17ecd1 --- /dev/null +++ b/config/archive/seed-modbus.yaml @@ -0,0 +1,48 @@ +esphome: + name: seed-modbus + friendly_name: seed modbus + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: arduino + +# Enable logging +logger: + +# Enable Home Assistant API +api: + encryption: + key: "+fvyzEhRt2I4BPtrS9dqhv/qCibX0azYMgMY1tkEOLw=" + +ota: + - platform: esphome + password: "c3274286d9131444822bed49abf1b77b" + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Seed-Modbus Fallback Hotspot" + password: "j9tvzwj9WPEe" + +captive_portal: + +output: + - platform: gpio + pin: GPIO21 #GPIO4 + id: led_output + +switch: + - platform: output + name: "LED" + output: led_output + id: led_switch + restore_mode: ALWAYS_OFF + +interval: + - interval: 1s + then: + - switch.toggle: led_switch \ No newline at end of file diff --git a/config/seed-modbus.yaml.save b/config/seed-modbus.yaml.save new file mode 100644 index 0000000..ba85c63 --- /dev/null +++ b/config/seed-modbus.yaml.save @@ -0,0 +1,117 @@ +esphome: + name: seed-modbus + friendly_name: seed modbus + on_boot: + then: + - output.turn_on: output1 + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: arduino + +# Enable logging +logger: + level: DEBUG + +# Enable Home Assistant API +api: + encryption: + key: "6wSuBPbrOHBZNl2RG7Dha1yjHddwhVbNK82ET4Pcz3I=" + +ota: + - platform: esphome + password: "6ce9a2a585eb85f9a150d88e6c676483" + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +uart: + id: uart_bus_a +# TX/RX carte : + rx_pin: GPIO44 + tx_pin: GPIO43 +# RX=D4 -> GPIO5, TX=D5 -> GPIO6 +# rx_pin: GPIO5 +# tx_pin: GPIO6 +# RX=D7, TX=D6 + baud_rate: 4800 + +output: + - platform: gpio + id: output1 + pin: + number: GPIO3 + mode: + output: True + +modbus: + id: modbus1 + uart_id: uart_bus_a + +modbus_controller: + - id: modbus_controller1 + address: 1 + modbus_id: modbus1 + update_interval: 2s + +# select: +# - platform: modbus_controller +# name: "Modbus Select Register 0" +# address: 1 +# value_type: U_WORD +# optionsmap: +# "Zero": 0 +# "One": 1 +# "Two": 2 +# "Three": 3 + + +# select: +# - platform: modbus_controller +# name: "Modbus Select Register 1" +# address: 1 +# value_type: U_WORD +# optionsmap: +# "Zero": 0 +# "One": 1 +# "Two": 2 +# "Three": 3 +# lambda: |- +# ESP_LOGD("Reg1000", "Received value %lld", x); + +mqtt: + broker: "pc-raymond-wifi.home" + id: mqtt1 + log_topic: rbo + +sensor: + - platform: modbus_controller + id: sensor1 + modbus_controller_id: modbus_controller1 + name: "Wind speed" + device_class: wind_speed + register_type: holding + address: 0 + unit_of_measurement: "m/s" + value_type: U_WORD + accuracy_decimals: 1 + filters: + - multiply: 0.1 + - max: + window_size: 5 + send_every: 5 + - exponential_moving_average: + alpha: 0.1 + send_every: 12 + - platform: template + id: mqtt_wind_speed + lambda: |- + ESP_LOGD("Reg1000", "Received value %lld", id(sensor1).state); + return id(sensor1).state; + on_value: + then: + - mqtt.publish: + topic: "home/wind_speed" + payload: !lambda 'return to_string(id(mqtt_wind_speed).state);' \ No newline at end of file diff --git a/config/seeed-studio.yaml b/config/seeed-studio.yaml new file mode 100644 index 0000000..085fec3 --- /dev/null +++ b/config/seeed-studio.yaml @@ -0,0 +1,116 @@ +esphome: + name: seeed-studio + friendly_name: seeed studio + on_boot: + then: + - output.turn_on: DE_RE + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: arduino + +# Enable logging +logger: + +# Enable Home Assistant API +api: + encryption: + key: "1tRYVfsGzt8mFr+ay9hE24pdPd8IACedduh9tLWg4xs=" + +ota: + - platform: esphome + password: "d68060a1d18b9d61354434595717070c" + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Seeed-Studio Fallback Hotspot" + password: "FrTglhXBIVqi" + +captive_portal: + +output: + - platform: gpio + pin: GPIO21 # Led de la carte + id: led_output + - platform: gpio + pin: GPIO3 # le pin connecté à DE/RE + id: DE_RE + +switch: + - platform: output + name: "LED" + output: led_output + id: led_switch + restore_mode: ALWAYS_OFF + +interval: + - interval: 5s + then: + - switch.toggle: led_switch + - logger.log: "rbo2" + +uart: + id: mod_bus_id + tx_pin: GPIO44 # Il faut les inverser !!!! + rx_pin: GPIO43 + baud_rate: 4800 + debug: + direction: BOTH + dummy_receiver: false + after: + delimiter: "\n" + sequence: + - lambda: UARTDebug::log_string(direction, bytes); + +modbus: + id: modbus1 + uart_id: mod_bus_id + +modbus_controller: + - id: modbus_controller1 + address: 1 + modbus_id: modbus1 + setup_priority: -10 + command_throttle: 2s + update_interval: 4s + +sensor: + - platform: modbus_controller + id: sensor1 + modbus_controller_id: modbus_controller1 + name: "Wind speed" + device_class: wind_speed + register_type: holding + address: 0 + unit_of_measurement: "m/s" + value_type: U_WORD + accuracy_decimals: 1 + filters: + - multiply: 0.1 + - max: + window_size: 1 + send_every: 1 + - min: + window_size: 1 + send_every: 1 + # - exponential_moving_average: + # alpha: 0.1 + # send_every: 12 + on_value: + then: + - mqtt.publish: + topic: "rbo2" + payload: !lambda 'return to_string(x);' + +mqtt: + broker: "pc-raymond.home" + id: mqtt1 + log_topic: rbo + birth_message: + topic: rbo2 + payload: 'send_every: 1 !' \ No newline at end of file diff --git a/mqtt/compose.yml b/mqtt/compose.yml new file mode 100644 index 0000000..cfef0a3 --- /dev/null +++ b/mqtt/compose.yml @@ -0,0 +1,9 @@ +services: + mqtt: + image: eclipse-mosquitto:2.0 + restart: unless-stopped + # volumes: + # - /grab/data/mosquitto:/mosquitto + ports: + - 1883:1883 + command: "mosquitto -c /mosquitto-no-auth.conf" diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..a60d62a --- /dev/null +++ b/notes.md @@ -0,0 +1,20 @@ +# Notes + +## Matériel + +### Anémomètre + +SN-3000-FSJT-N01 + +``` +Nom : Lecteur de pare-brise en polycarbonate 485 +Alimentation : 10~30V +Séquence de lignes : + Marron : alimentation positive + Noir : alimentation négative + Jaune (vert) : 485 A + Bleu : 485 B +Numéro de lot : 2502 +Propriétaire : Shandong Saien Electronic Technology Co., Ltd. +Adresse : n° 1193, route Gangyuan, ville de Jinan, province du Shandong +``` diff --git a/proto/configModel.py b/proto/configModel.py new file mode 100644 index 0000000..e1ed201 --- /dev/null +++ b/proto/configModel.py @@ -0,0 +1,58 @@ +from typing import Optional +from enum import Enum +from dataclasses import dataclass + +class Algo(Enum): + IEEE2 = "ieee754_sur_2_registres" + NORMAL = "lecture_directe" + STRING = "Chaîne de caractères" + UINT32 = "Entier sur 32 bits non signé" + BOOLEAN = "Booléen" + +@dataclass +class Metrique: + # Id de la métrique pour ce capteur : + idMetrique:int + indexRegistreDepart:int + indexRegistreFin:int = None # Ne sert que dans le cas de Algo.STRING + # Nombre de chiffres après la virgule (utilisé si Algo.IEEE2) + precision:int = 2 + algo:Algo = Algo.NORMAL + +class ModbusType(Enum): + RTU = "rtu" + TCP = "tcp" + +@dataclass +class RtuConfig: + baudrate:int + stopbits:int + bytesize:int + parity:str + serialPort:str = "/dev/ttyACM0" + +@dataclass +class TcpConfig: + ip:str + +@dataclass +class AppelCapteur: + firstRegisterAdress:int + nbRegisters:int + # Les métriques associées à ce capteur + metriques:list[Metrique] + # Le délais, en secondes, entre 2 appels (et stockage en bdd des métriques liées) avec 15s comme valeur par défaut : + delai:int = 15 + +@dataclass +class Capteur: + idCapteur:int + modbusAdresse:int + appels:list[AppelCapteur] + +@dataclass +class Canal: + description:str + typeModbus:ModbusType + comConfig:RtuConfig|TcpConfig + capteurs:list[Capteur] diff --git a/proto/requirements.txt b/proto/requirements.txt new file mode 100644 index 0000000..d1cccc2 --- /dev/null +++ b/proto/requirements.txt @@ -0,0 +1,7 @@ +APScheduler==3.10.4 +psycopg2-binary==2.9.10 +pymodbus==3.7.0 +pyserial==3.5 +pytz==2024.1 +six==1.16.0 +tzlocal==5.2 diff --git a/proto/t.py b/proto/t.py new file mode 100755 index 0000000..ffdcc51 --- /dev/null +++ b/proto/t.py @@ -0,0 +1,60 @@ +#!python +from pymodbus.client import ModbusSerialClient, ModbusTcpClient +from pymodbus.pdu.register_read_message import ReadInputRegistersResponse +from configModel import Algo, Metrique, Capteur, ModbusType +import struct, time + +# client = ModbusTcpClient(host="shellyproem50-08f9e0e79718") # grarage (borne et pac) +# client = ModbusTcpClient(host="ShellyPro3EM-FCE8C0D97664") # bureau (prises 1 à 3) +client = ModbusSerialClient( + port="/dev/ttyACM0", + baudrate=4800, + # stopbits=capteur.comConfig.stopbits, + # bytesize=capteur.comConfig.bytesize, + # parity=capteur.comConfig.parity +) + +def ieee754(registres:list): + float_bytes = struct.pack('HH', registres[0], registres[1]) + return struct.unpack('f', float_bytes)[0] + +def uint32(a, b): + ret:int = (a << 16) | b + return ret + +def lireMetrique(metrique:Metrique, registre): + client.connect() + try: + data:ReadInputRegistersResponse = client.read_input_registers(registre, 2, slave=1) + if data: + print(f"--> {data}") + registres = data.registers + if metrique.algo == Algo.NORMAL: + val = registres[0] + if metrique.algo == Algo.IEEE2: + val = round(ieee754(registres[0:2]), metrique.precision) + if metrique.algo == Algo.UINT32: + val = uint32(registres[0], registres[1]) + if metrique.algo == Algo.STRING: + val = ''.join(chr(register) for register in data.registers) + if metrique.algo == Algo.BOOLEAN: + val = registres[0] + print(f"Captation sur {registre} : {val}") + except Exception as e: + print(f"Erreur lors de la captation sur {registre} : {e}") + finally: + client.close() + +registre = -1 + +if __name__ == '__main__': + while True: + registre = 0 + m:Metrique = Metrique( + idMetrique=1, + indexRegistreDepart=registre, + precision=3, + algo=Algo.NORMAL + ) + lireMetrique(m, registre) + time.sleep(1)