Poolsteuerung für PH-803W, wer kann dieses schreiben, Vorarbeit ist geleistet.

Begonnen von coolheizer, 07 September 2022, 10:00:02

Vorheriges Thema - Nächstes Thema

coolheizer

Es gibt den PH-803W der sehr kostengünstig den PH und Redox Wert des Wassers ausliest, und durch schaltbare Steckdosen dieses regulieren kann.
Hier können Pumpen für PH/ Chlor oder ein Chlorinator angeschlossen werden, Daten werden über W-Lan auf einen China Server übertragen, und können per App ausgelesen werden.

Appolon77 hat bereits das ganze ausgewertet und Dukumentiert, sowie ein Modul für ioBroker geschrieben, hier läuft es scheinbar bereits.

Das Protokoll hat er auf Github veröffentlicht https://github.com/Apollon77/node-ph803w/blob/main/PROTOCOL.md


Das ganze habe ich im PoolPowershop aufgegriffen, dort hat alf_1 ein Skript https://www.poolpowershop-forum.de/forum/thread/1145420-controller-ph-803-jetzt-mit-w-lan/?postID=1245378#post1245378 geschrieben das auf die Auswertung von Appolon77 aufbaut:

#!/usr/bin/perl -w

# Simple proof of concept program to get pH and ORP (redox) values from a PH-803W
# inside your local network without the PH803 app and without the gizwits cloud
# (the app is still needed to initially connect the PH-803W to your WLAN)
#
# No auto discovery, IP address of PH-803W must be known
# (change my $PH_803_IP = ... below)
#
# Based on the protocol from https://github.com/Apollon77/node-ph803w/blob/main/PROTOCOL.md
# and code from https://www.xmodulo.com/how-to-write-simple-tcp-server-and-client-in-perl.html

use strict;
use IO::Socket::INET;

my $PH_803_IP = '192.168.5.3';

sub hex2bytes {
    my ($hex) = @_;
    my $bytes = pack('H*', $hex);
    return $bytes;
}

sub bytes2hex {
    my ($bytes) = @_;
    my $hex = unpack('H*', $bytes);
    return $hex;
}

# send a hex string to the server (convert to bytes before)
sub sendhex {
    my ($socket, $datah) = @_;

    my $datab = hex2bytes($datah);
    my $len = length($datab);
    my $sentlen = $socket->send($datab);
    if ($sentlen != $len) {
        die "failed to send all $len bytes of $datah, only sent $sentlen";
    }
    print "sent $datah\n";
}

# receive a response of up to 1024 characters from server (returned as a hex string)
sub receivehex {
    my ($socket) = @_;

    my $datab = '';
    $socket->recv($datab, 1024);
    my $datah = bytes2hex($datab);
    print "received $datah\n";
    return $datah;
}

# verify a response from the server against an expected value (meant for hex strings)
sub verifyhex {
    my ($resph, $expectedh) = @_;
    if ($resph ne $expectedh) {
        die "invalid response: received $resph, expected $expectedh";
    }
    print "verified response $resph\n";
}

# convert a 4 character hex string to a unsigned 16 bit short value
sub hex2ushort {
    my ($datah) = @_;
    die "input must be a hex string of 4 characters: $datah" unless length($datah) == 4;
    my $ushort = unpack('n', hex2bytes($datah));
    return $ushort;
}

# auto-flush on socket
$| = 1;

# create a connecting socket
my $socket = new IO::Socket::INET (
    PeerHost => $PH_803_IP,
    PeerPort => '12416',
    Proto => 'tcp',
);

if ($socket) {
    print "connected to the PH-803W server $PH_803_IP\n";
} else {
    die "failed to connect to the PH-803W server $PH_803_IP: $!" unless $socket;
}

# step 1:
# send 00 00 00 03 03 00 00 06
# and verify that response starts with 00 00 00 03 0f 00 00 07

my $req1h = '0000000303000006';
sendhex($socket, $req1h);

my $resp1h = receivehex($socket);
verifyhex(substr($resp1h,0,16), '000000030f000007');

# step 2:
# send response from step 1, but with byte 8 changed from 07 to 08,
# and verify that response is 00 00 00 03 04 00 00 09 00

my $req2h = substr($resp1h,0,14) . '08' . substr($resp1h,16);
sendhex($socket, $req2h);

my $resp2h = receivehex($socket);
verifyhex($resp2h, '000000030400000900');

# step 3:
# send 00 00 00 03 04 00 00 90 02
# and verify that response starts with 00 00 00 03 0d 00 00 91

my $req3h = '000000030400009002';
sendhex($socket, $req3h);

my $resp3h = receivehex($socket);
verifyhex(substr($resp3h, 0, 16), '000000030d000091');

# response from step 3 contains pH value at bytes 11/12,
# must be interpreted as an unsigned 16 bit value and divided by 100

my $phh = substr($resp3h, 20, 4);
my $ph = hex2ushort($phh) / 100;

# response from step 3 contains ORP value at bytes 13/14,
# must be interpreted as an unsigned 16 bit value and decreased by 2000

my $orph = substr($resp3h, 24, 4);
my $orp = hex2ushort($orph) - 2000;

print "pH=$ph orp=$orp mV\n";
# add code here to do something useful with the values, e.g. send them to MQTT

$socket->close();



andyyyy hat wiederum das ganze erweitert so das Werte ins Domoticz per mqtt geliefert werden.

Darauf baut wiederum das funktionierende Skript auf welches Ich hier vorstellen möchte, großen dank hierbei an alf_1 , alleine hätte ich das nicht hinbekommen.


Ich lasse das Skript alle 3 Minuten per Cronjob ausführen, es werden die Schaltzustände von PH/ Redox sowie die Werte für selbiges per Mqtt übergeben und stehen somit in Fhem zur weiteren Verarbeitung zur verfügung:

#!/usr/bin/perl -w


# Simple proof of concept program to get pH and ORP (redox) values from a PH-803W
# inside your local network without the PH803 app and without the gizwits cloud
# (the app is still needed to initially connect the PH-803W to your WLAN)
#
# No auto discovery, IP address of PH-803W must be known
# (change my $PH_803_IP = ... below)
#
# Based on the protocol from https://github.com/Apollon77/node-ph803w/blob/main/PROTOCOL.md
# and code from https://www.xmodulo.com/how-to-write-simple-tcp-server-and-client-in-perl.html
# Special thanks to alf_1 !!


use strict;
use IO::Socket::INET;
use warnings;
use autodie;
use Net::MQTT::Simple;


my $PH_803_IP = '191.168.178.92';


# idx für PH und ORP

#my $idxph = '111';
#my $idxorp = '222';


# MQTT

my $mqtt = Net::MQTT::Simple->new('localhost:1880');

my $mqtt_username = 'Name';
my $mqtt_password = 'Passwort';


# Allow unencrypted connection with credentials

$ENV{MQTT_SIMPLE_ALLOW_INSECURE_LOGIN} = 1;



sub hex2bytes {
my ($hex) = @_;
my $bytes = pack('H*', $hex);
return $bytes;
}


sub bytes2hex {
my ($bytes) = @_;
my $hex = unpack('H*', $bytes);
return $hex;
}


# send a hex string to the server (convert to bytes before)
sub sendhex {
my ($socket, $datah) = @_;

my $datab = hex2bytes($datah);
my $len = length($datab);
my $sentlen = $socket->send($datab);
if ($sentlen != $len) {
die "failed to send all $len bytes of $datah, only sent $sentlen";
}
print "sent $datah\n";
}


# receive a response of up to 1024 characters from server (returned as a hex string)
sub receivehex {
my ($socket) = @_;

my $datab = '';
$socket->recv($datab, 1024);
my $datah = bytes2hex($datab);
print "received $datah\n";
return $datah;
}

# verify a response from the server against an expected value (meant for hex strings)
sub verifyhex {
my ($resph, $expectedh) = @_;
if ($resph ne $expectedh) {
die "invalid response: received $resph, expected $expectedh";
}
print "verified response $resph\n";
}


# convert a 4 character hex string to a unsigned 16 bit short value
sub hex2ushort {
my ($datah) = @_;
die "input must be a hex string of 4 characters: $datah" unless length($datah) == 4;
my $ushort = unpack('n', hex2bytes($datah));
return $ushort;
}


# auto-flush on socket
$| = 1;

# create a connecting socket
my $socket = new IO::Socket::INET (
PeerHost => $PH_803_IP,
PeerPort => '12416',
Proto => 'tcp',
);


#if ($socket) {
#print "connected to the PH-803W server $PH_803_IP\n";
#} else {

# Depending if authentication is required, login to the broker
#if($mqtt_username and $mqtt_password) {
#$mqtt->login($mqtt_username, $mqtt_password);
#}

# Publish Error
#$mqtt->publish("domoticz/in", "{\"command\" : \"addlogmessage\", \"message\" : \"PH-803W: Keine Verbindung zu PH-803W ($PH_803_IP)\" }");
#$mqtt->publish("domoticz/in", "{\"command\" : \"addlogmessage\", \"message\" : \"PH-803W: näschster Versuch in 5 Minuten\" }");
#$mqtt->disconnect();

#die "failed to connect to the PH-803W server $PH_803_IP: $!" unless $socket;
#}


# step 1:
# send 00 00 00 03 03 00 00 06
# and verify that response starts with 00 00 00 03 0f 00 00 07

my $req1h = '0000000303000006';
sendhex($socket, $req1h);

my $resp1h = receivehex($socket);
verifyhex(substr($resp1h,0,16), '000000030f000007');

# step 2:
# send response from step 1, but with byte 8 changed from 07 to 08,
# and verify that response is 00 00 00 03 04 00 00 09 00

my $req2h = substr($resp1h,0,14) . '08' . substr($resp1h,16);
sendhex($socket, $req2h);

my $resp2h = receivehex($socket);
verifyhex($resp2h, '000000030400000900');


# step 3:
# send 00 00 00 03 04 00 00 90 02
# and verify that response starts with 00 00 00 03 0d 00 00 91

my $req3h = '000000030400009002';
sendhex($socket, $req3h);

my $resp3h = receivehex($socket);
verifyhex(substr($resp3h, 0, 16), '000000030d000091');

# response from step 3 contains pH value at bytes 11/12,
# must be interpreted as an unsigned 16 bit value and divided by 100

my $phh = substr($resp3h, 20, 4);
my $ph = hex2ushort($phh) / 100;

# response from step 3 contains ORP value at bytes 13/14,
# must be interpreted as an unsigned 16 bit value and decreased by 2000

my $orph = substr($resp3h, 24, 4);
my $orp = hex2ushort($orph) - 2000;

my $orph1 = substr($resp3h, 19, 1);
#my $orp1 = hex2ushort($orph1) - 768;

my $orpschalt = '?';

my $phschalt = '?';

if ($orph1 eq '3') {

$orpschalt = '1';

$phschalt = '1';

} elsif ($orph1 eq '2') {

$orpschalt = '1';

$phschalt = '0';

} elsif ($orph1 eq '1') {

$orpschalt = '0';

$phschalt = '1';

} elsif ($orph1 eq '0') {

$orpschalt = '0';

$phschalt = '0';

}




print "pH=$ph orp=$orp mV ph_schalt=$phschalt orp_schalt=$orpschalt \n ";
# add code here to do something useful with the values, e.g. send them to MQTT

# Depending if authentication is required, login to the broker
if($mqtt_username and $mqtt_password) {
$mqtt->login($mqtt_username, $mqtt_password);
}

# Publish Messwerte
#$mqtt->publish("pool/in", "{\"idx\":$idxph,\"svalue_ph\":\"$ph\"}");
$mqtt->publish("pool/in", "{\"svalue_ph\":\"$ph\"}");
$mqtt->publish("pool/in", "{\"svalue_orp\":\"$orp\"}");
#$mqtt->publish("pool/in", "{\"on/off_orp\":\"$orph1\"}");
$mqtt->publish("pool/in", "{\"ph_schalt\":\"$phschalt\"}");
$mqtt->publish("pool/in", "{\"orp_schalt\":\"$orpschalt\"}");
$mqtt->disconnect();

$socket->close();



Wäre klasse wenn jemand daraus ein Modul für Fhem schreiben könnte.


Karsten
FHEM 5.8 auf Raspberry Pi 3, HM-MOD-UART und  MapleCUN.
HM-MOD-Re-8 für Velux Rolladensteuerung.
HM-CC-RT-DN.
HM-SEC-SCo.
HM-LC-Bl1PBU-FM.