diff --git a/iec1107.pm b/iec1107.pm deleted file mode 100755 index 9f7387c..0000000 --- a/iec1107.pm +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/perl -# -# perl module for accessing a IEC1107 device -# This is my first my perl module and these resources were a good kickstart -# https://learn.perl.org/books/beginning-perl/ -# https://wiki.volkszaehler.org/hardware/channels/meters/power/eastron_drs155m -# Menschel (C) 2020-2021 - -package iec1107; # we name our package iec1107 as this is the original protocol name - -use strict; -use warnings; -use Carp; - -use Device::SerialPort; - -#for time conversion -use DateTime::Format::Strptime qw( strptime ); -use DateTime; - -#constants -our $SOH = chr(0x01); -our $STX = chr(0x02); -our $ETX = chr(0x03); -our $EOT = chr(0x04); - -our $ACK = chr(0x06); -our $NACK = chr(0x15); - -our $CRLF = "\r\n"; -our $STARTCHARACTER = "/"; -our $TRANSMISSIONREQUESTCOMMAND = "?"; -our $ENDCHARACTER = "!"; - -our %drs110m_values = ( - #''=>[
,,''], - 'Voltage' =>[ 0, \&_scale_div_by_10, 'V'], - 'Current' =>[ 1, \&_scale_div_by_10, 'A'], - 'Frequency' =>[ 2, \&_scale_div_by_10, 'Hz'], - 'Active Power' =>[ 3, \&_scale_mul_by_10, 'W'], - 'Reactive Power'=>[ 4, \&_scale_mul_by_10,'VAr'], - 'Apparent Power'=>[ 5, \&_scale_mul_by_10, 'VA'], - 'Active Energy' =>[10, \&_scale_1_to_1, 'Wh'], - 'Time' =>[31, \&_scale_raw_time_to_datetime, ''], - 'Temperature' =>[32, \&_scale_to_temp, '°C'], - ); -#actually there are more registers, but who cares about cosphi for example?! - -sub new(\$$$){ - #we expect a HASH consisting of a reference to a valid and correctly set up port, an ID and a password - # {"port"=>$port, #perl automatically converts this to a reference - # "id"=>$id, - # "passwd"=>$passwd, - #} - my $class = shift; - my $self = {@_}; - bless($self,$class); - $self->_init; - return $self; -}; - -sub _init { - my $self = shift; - $self->{"regs"} = (); - return; -}; - -sub start_communication { - my $self = shift; - unless (ref $self){croak "call with an object, not a class";} - my $res; - $res = $self->_xfer(_generate_request_message("serialnumber"=>$self->id)); - #Note: There is an automatic sleep from the serial timeout, so we don't sleep here - if (!$res){ - #a second wakeup call is not required every time, only when the device was asleep. - $res = $self->_xfer(_generate_request_message("serialnumber"=>$self->id)); - }; - return $self; -}; - -sub start_programming_mode { - my $self = shift; - unless (ref $self){croak "call with an object, not a class";} - my $res; - $res = $self->_xfer(_generate_ack_optionselect_msg("protocol"=>0,"mode"=>1)); - #note: mode 1 is programming mode, obviously privileges are needed for register access - $res = $self->_xfer(_generate_p1_msg("password"=>$self->passwd)); - return $self; -}; - - -sub update_values { - my $self = shift; - unless (ref $self){croak "call with an object, not a class";} - my $res; - my $valstr; - my $unit; - my ($addr,$val); - while ( my ($measurement, $vals) = each(%drs110m_values) ) { - $res = $self->_xfer(_generate_r1_msg("reg"=>$drs110m_values{$measurement}[0])); - ($addr,$val) = _interpret_r1_msg($res); - if (defined($addr)){ - if ($addr == $drs110m_values{$measurement}[0]){ - $val = &{$drs110m_values{$measurement}[1]}($val); - $unit = $drs110m_values{$measurement}[2]; - $valstr = sprintf("%s %s",$val,$unit); - $self->{regs}{$measurement}=$valstr; - } - else{ - die("Found $addr but expected $drs110m_values{$measurement}[0]"); - }; - } - else { - die("No Response for $measurement"); - }; - } - - return $self; -}; - -sub log_off() { - my $self = shift; - my $res; - unless (ref $self){croak "call with an object, not a class";} - $res = $self->_xfer(_generate_b0_msg()); - return $self; -}; - -sub write_reg($$) { - my $self = shift; - my ($reg, $val) = @_; - unless (ref $self){croak "call with an object, not a class";} - - my $res = $self->_xfer(_generate_w1_msg("reg"=>$reg, "val"=>$val)); - if ($res ne $ACK) { - print("No Ack for write operation $reg : $val \n"); - }; - return $self; -}; - -sub get_values() { - my $self = shift; - my $res; - unless (ref $self){croak "call with an object, not a class";} - $self->start_communication()->start_programming_mode()->update_values()->log_off(); - return $self->regs; -}; - -sub set_clock() { - my $self = shift; - my $res; - unless (ref $self){croak "call with an object, not a class";} - $self->start_communication()->start_programming_mode(); - $res = $self->write_reg(31, _scale_datetime_to_raw_time(DateTime->now())); - # this function reliably returns an ACK - $self->log_off(); - return $self; -}; - -sub reset_energy() { - my $self = shift; - my $res; - unless (ref $self){croak "call with an object, not a class";} - $self->start_communication()->start_programming_mode(); - $res = $self->write_reg(0x40, "00000000"); - # this function does not reliably return an ACK, to be checked - $self->log_off(); - return $self; -}; - -sub _xfer { - my $self = shift; - my ($cmd) = @_; - my $count; - my $res; - $self->port->lookclear; - $self->port->write( $cmd ); - ($count,$res)=$self->port->read(32); - return $res; -}; - - -# Object accessor methods -sub port { $_[0]->{port}=$_[1] if defined $_[1]; $_[0]->{port} } -sub id { $_[0]->{id}=$_[1] if defined $_[1]; $_[0]->{id} } -sub passwd { $_[0]->{passwd}=$_[1] if defined $_[1]; $_[0]->{passwd} } -sub regs { $_[0]->{regs}=$_[1] if defined $_[1]; $_[0]->{regs} } - - - - -#basic non-object functions -sub _interpret_r1_msg($){ - my ($str) = @_; - my $val; - my $addr; - if($str =~ m/\((\S+)\)/) { - $val = $1; - if($str =~ m/(\d+)\(/) { - $addr = $1; - }; - }; - return $addr,$val; -}; - - -sub _scale_div_by_10($){ - my ($val) = @_; - return $val/10; -}; - -sub _scale_mul_by_10($){ - my ($val) = @_; - return $val*10; -}; - -sub _scale_1_to_1($){ - my ($val) = @_; - return $val; -}; - -sub _scale_raw_time_to_datetime($){ - my ($str) = @_; - my $fmt = "%y%m%d%w%H%M%S"; - my $dt = strptime($fmt, $str); - return $dt; -}; - -sub _scale_datetime_to_raw_time($){ - my ($dt) = @_; - my $fmt = "%y%m%d0%w%H%M%S"; - my $str = $dt->strftime($fmt); - return $str; -}; - -sub _scale_to_temp($){ - 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_w1_msg(%){ - my %args = @_; - my $reg = $args{reg}; - my $val = $args{val}; - my $regstr = sprintf("%08d(%d)",$reg,$val); - my $msg=_generate_programming_command_message("command"=>"W","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=$SOH.$cmdstr.$STX.$data.$ETX; - $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=$ACK.$msgstr.$CRLF; - return $msg; -}; - - -sub _generate_request_message(%){ - my %args = @_; - my $serialnumber = $args{serialnumber}; - my $snstr = sprintf("%012d",$serialnumber); - my $msg = $STARTCHARACTER.$TRANSMISSIONREQUESTCOMMAND.$snstr.$ENDCHARACTER.$CRLF; - return $msg; -}; - - -1; diff --git a/test_drs110m.pl b/test_drs110m.pl index f550c05..b11b0b3 100755 --- a/test_drs110m.pl +++ b/test_drs110m.pl @@ -13,12 +13,6 @@ my $workdir = getcwd; push @INC, "$workdir/lib"; -# sub BEGIN { -# push @INC, "$workdir/lib"; - -# return; -# } - require iec1107; my $port = Device::SerialPort->new(