initial commit
This commit is contained in:
		@@ -2,3 +2,4 @@
 | 
			
		||||
A perl script to communication with a drs110m din rail power meter similar to my
 | 
			
		||||
[pyehz](https://github.com/menschel/pyehz) project. This is considered a pre-step for 
 | 
			
		||||
integration in FHEM OBIS Module.
 | 
			
		||||
Use and Copy as you wish.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										253
									
								
								test_drs110m.pl
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										253
									
								
								test_drs110m.pl
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,253 @@
 | 
			
		||||
#!/usr/bin/perl
 | 
			
		||||
# 
 | 
			
		||||
# This is actually my first attempt with perl. 
 | 
			
		||||
# This script borrows parts of https://wiki.volkszaehler.org/hardware/channels/meters/power/eastron_drs155m
 | 
			
		||||
# which itself borrows parts of http://www.ip-symcon.de/forum/threads/21407-Stromz%C3%A4hler-mit-RS485/page2
 | 
			
		||||
# The general functions have been developed in python3, see https://github.com/menschel/pyehz
 | 
			
		||||
# Use and copy as you wish.
 | 
			
		||||
# Menschel (C) 2019
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
 
 | 
			
		||||
use Device::SerialPort;
 | 
			
		||||
 | 
			
		||||
#for time conversion
 | 
			
		||||
use POSIX::strptime qw( strptime );
 | 
			
		||||
use POSIX qw{strftime};
 | 
			
		||||
 
 | 
			
		||||
my $port = Device::SerialPort->new("/dev/ttyUSB0") || die $!;
 | 
			
		||||
$port->baudrate(9600);
 | 
			
		||||
$port->databits(7);
 | 
			
		||||
$port->parity("even");
 | 
			
		||||
$port->stopbits(1);
 | 
			
		||||
$port->handshake("none");
 | 
			
		||||
$port->write_settings;
 | 
			
		||||
 
 | 
			
		||||
$port->purge_all();
 | 
			
		||||
$port->read_char_time(0);     # don't wait for each character
 | 
			
		||||
$port->read_const_time(100); # 100 millisecond per unfulfilled "read" call
 | 
			
		||||
 
 | 
			
		||||
my $serialID = "001613300153";        # The serial number of the specific device 12-digits long.
 | 
			
		||||
# It is possible to find out the device id of a single device on RS-485 9600@7E1 by sending "/?!\r\n"
 | 
			
		||||
my $password = "00000000"; # Standard password 0 over 8-digits
 | 
			
		||||
 
 | 
			
		||||
my $verbose = 2 ;
 | 
			
		||||
 
 | 
			
		||||
# ========================================
 | 
			
		||||
sub sendgetserial {
 | 
			
		||||
  my ($cmd) = @_;
 | 
			
		||||
  my $count;
 | 
			
		||||
  my $saw;
 | 
			
		||||
  my $x;
 | 
			
		||||
 
 | 
			
		||||
  $port->lookclear;
 | 
			
		||||
  $port->write( $cmd );
 | 
			
		||||
 
 | 
			
		||||
  ($count,$saw)=$port->read(84);   # will read 84 chars
 | 
			
		||||
  $x=uc(unpack('H*',$saw)); # nach hex wandeln
 | 
			
		||||
 
 | 
			
		||||
  $cmd =~ s/\n/\\n/mg;
 | 
			
		||||
  $cmd =~ s/\r/\\r/mg;
 | 
			
		||||
 
 | 
			
		||||
  $saw =~ s/\n/\\n/mg;
 | 
			
		||||
  $saw =~ s/\r/\\r/mg;
 | 
			
		||||
 
 | 
			
		||||
  if ( $verbose>10 ) {
 | 
			
		||||
    printf "+++ sendserial\n" ;
 | 
			
		||||
    print  " CMD: $cmd \n";  # gibt den Befehl in ASCII aus
 | 
			
		||||
    print  " COUNT: $count \n";  # gibt die Anzahl der empfangenen Daten aus
 | 
			
		||||
    print  " HEX: $x \n";  # gibt die empfangenen Daten in Hex aus
 | 
			
		||||
    print  " ASCII: $saw \n";  # gibt die empfangenen Daten aus
 | 
			
		||||
    printf "--- sendserial\n" ;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  return $saw;
 | 
			
		||||
}
 | 
			
		||||
# ========================================
 | 
			
		||||
sub decodeVAL {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
 
 | 
			
		||||
  if ( $verbose>10 ) {
 | 
			
		||||
    printf "+++ decodeVAL\n" ;
 | 
			
		||||
    print " val = ( $val ) \n" ;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  if($val =~ m/\((\d+)\)/) {
 | 
			
		||||
    if ( $verbose>10 ) {
 | 
			
		||||
      print " decoded val = $1\n";
 | 
			
		||||
      printf " --- decodeVAL\n" ;
 | 
			
		||||
    }
 | 
			
		||||
    return $1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if($val =~ m/\((\S+)\)/) {#string value for temperature
 | 
			
		||||
    if ( $verbose>10 ) {
 | 
			
		||||
      print " decoded val = $1\n";
 | 
			
		||||
      printf " --- decodeVAL\n" ;
 | 
			
		||||
    }
 | 
			
		||||
    return $1;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  print " val = ( $val ) \n" ;
 | 
			
		||||
  die "NICHTS gefunden!\n";
 | 
			
		||||
  print "NICHTS gefunden!\n";
 | 
			
		||||
  return -8888;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub decodeVal1decimal {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
  return $val/10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub decodeVal10times {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
  return $val*10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub decodeVal1to1 {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
  return $val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub decodeValTime {
 | 
			
		||||
#"19112703192714" => 2019-11-27 19:27:14 
 | 
			
		||||
  my ($str) = @_;
 | 
			
		||||
  #print("$str \n");
 | 
			
		||||
  my $fmt = "%y%m%d0%w%H%M%S";
 | 
			
		||||
  my @time = (POSIX::strptime($str,$fmt))[0..7];
 | 
			
		||||
  #print("@time \n");
 | 
			
		||||
  return @time;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub decodeValTemp {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
  my $hex = "";
 | 
			
		||||
  foreach (split '',$val){
 | 
			
		||||
      $hex .= sprintf("%X", ord($_)-0x30);
 | 
			
		||||
    };
 | 
			
		||||
  return hex($hex);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
sub calc_bcc {
 | 
			
		||||
  my ($val) = @_;
 | 
			
		||||
  my $bcc = 0;
 | 
			
		||||
  foreach (split'',substr($val,1)){
 | 
			
		||||
    $bcc ^= ord($_);
 | 
			
		||||
  }
 | 
			
		||||
  return $bcc;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub generate_r1_msg{
 | 
			
		||||
  my %args = @_;
 | 
			
		||||
  my $reg = $args{reg};
 | 
			
		||||
  my $regstr = sprintf("%08d()",$reg);
 | 
			
		||||
  my $msg=generate_programming_command_message("command"=>"R","commandtype"=>1,"data"=>$regstr);
 | 
			
		||||
  return $msg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub generate_p1_msg{
 | 
			
		||||
  my %args = @_;
 | 
			
		||||
  my $passwd = $args{password};
 | 
			
		||||
  my $passwdstr = sprintf("(%08d)",$passwd);
 | 
			
		||||
  my $msg=generate_programming_command_message("command"=>"P","commandtype"=>1,"data"=>$passwdstr);
 | 
			
		||||
  return $msg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub generate_b0_msg{
 | 
			
		||||
  my $msg=generate_programming_command_message("command"=>"B","commandtype"=>0,"data"=>"");
 | 
			
		||||
  return $msg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub generate_programming_command_message{
 | 
			
		||||
  my %args = @_;
 | 
			
		||||
  my $command = $args{command};
 | 
			
		||||
  my $commandtype = $args{commandtype};
 | 
			
		||||
  my $data = $args{data};
 | 
			
		||||
  my $cmdstr = sprintf("%s%d",$command,$commandtype);
 | 
			
		||||
  my $msg=chr(0x01).$cmdstr.chr(0x02).$data.chr(0x03);
 | 
			
		||||
  $msg .= chr(calc_bcc($msg));
 | 
			
		||||
  return $msg;  
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub generate_ack_optionselect_msg{
 | 
			
		||||
  my %args = @_;
 | 
			
		||||
  my $protocol = $args{protocol};
 | 
			
		||||
  my $mode = $args{mode};
 | 
			
		||||
  my $msgstr = sprintf("%d:%d",$protocol,$mode);#the ':' is the baudrate identifier
 | 
			
		||||
  my $msg=chr(0x06).$msgstr.chr(0x0D).chr(0x0A);#Todo: make the special characters nicely, note there is no bcc for this msg type
 | 
			
		||||
  return $msg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub generate_request_message{
 | 
			
		||||
  my %args = @_;
 | 
			
		||||
  my $serialnumber = $args{serialnumber};
 | 
			
		||||
  my $msg = sprintf("/?%012d!\r\n",$serialnumber);
 | 
			
		||||
  return $msg;  
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
# ========================================
 | 
			
		||||
 
 | 
			
		||||
#main() starts here 
 | 
			
		||||
 | 
			
		||||
my $cmd;
 | 
			
		||||
my $res;
 | 
			
		||||
my %vals = ();
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial(generate_request_message("serialnumber"=>$serialID));
 | 
			
		||||
#there is an automatic sleep from the serial timeout
 | 
			
		||||
if (!$res){
 | 
			
		||||
  #a second wakeup call is not required every time but when the device was asleep.
 | 
			
		||||
  $res = sendgetserial(generate_request_message("serialnumber"=>$serialID));
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
 
 | 
			
		||||
$res = sendgetserial(generate_ack_optionselect_msg("protocol"=>0,"mode"=>1));#note: mode 1 is programming mode, obvious privileges are needed for register access
 | 
			
		||||
$res = sendgetserial(generate_p1_msg("password"=>$password));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
#read registers, Todo: make this a loop over a HASH for easy usage
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>0) );
 | 
			
		||||
$vals { 'Voltage' } = decodeVal1decimal( decodeVAL $res  );
 | 
			
		||||
 
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>1) );
 | 
			
		||||
$vals { 'Current' } = decodeVal1decimal( decodeVAL $res  );
 | 
			
		||||
 
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>2) );
 | 
			
		||||
$vals { 'Frequency' } = decodeVal1decimal( decodeVAL $res  );
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>3) );
 | 
			
		||||
$vals { 'Active Power' } = decodeVal10times( decodeVAL $res  );
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>4) );
 | 
			
		||||
$vals { 'Reactive Power' } = decodeVal10times( decodeVAL $res  );
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>5) );
 | 
			
		||||
$vals { 'Apparent Power' } = decodeVal10times( decodeVAL $res  );
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>6) );
 | 
			
		||||
$vals { 'cosphi' } = decodeVal1to1( decodeVAL $res  ) / 1000 ;
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>10) );
 | 
			
		||||
$vals { 'Active Energy' } = decodeVal1to1( decodeVAL $res  ) * 1 ;
 | 
			
		||||
 
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>31) );
 | 
			
		||||
$vals { 'Time' } = strftime("%Y-%m-%d %H:%M:%S",decodeValTime( decodeVAL $res  ));
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial( generate_r1_msg("reg"=>32) );
 | 
			
		||||
$vals { 'Temperature' } = decodeValTemp( decodeVAL $res  ) ;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
$res = sendgetserial(generate_b0_msg());
 | 
			
		||||
 | 
			
		||||
while ( my ($key, $value) = each(%vals) ) {
 | 
			
		||||
  my $str = sprintf("%15s : %s \n",$key,$value);
 | 
			
		||||
  print($str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user