FEAT : Anémotrètre en modbus -> mqtt OK :-)

This commit is contained in:
Raymond Bourges 2025-05-24 10:30:00 +02:00
commit 4ca0462d58
11 changed files with 456 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
venv/

14
compose.yml Normal file
View File

@ -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

5
config/.gitignore vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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);'

116
config/seeed-studio.yaml Normal file
View File

@ -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 !'

9
mqtt/compose.yml Normal file
View File

@ -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"

20
notes.md Normal file
View File

@ -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
```

58
proto/configModel.py Normal file
View File

@ -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]

7
proto/requirements.txt Normal file
View File

@ -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

60
proto/t.py Executable file
View File

@ -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)