Device-WS2500PC

 view release on metacpan or  search on metacpan

lib/Device/WS2500PC.pm  view on Meta::CPAN

# # *** Library for interfacing the serial port of the WS2500PC Adapter      ***
# # *** Produced by German Distributor ELV                                   ***
# # ****************************************************************************
# # *** This program is free software; you can redistribute it and/or modify ***
# # *** it under the terms of the GNU General Public License as published by ***
# # *** the Free Software Foundation; either version 2 of the License, or    ***
# # *** (at your option) any later version.                                  ***
# # ****************************************************************************
# # *** History: 0.99   Initial release                                      ***
# # ***          0.99a  Bugfix in distribution                               ***
# # ***          0.99b  Bugfix for reading other sensors than temp1-temp8    ***
# # ***                 ws2500_GetDatasetBulk() added                        *** 
# # ****************************************************************************



# ********************************************************
# *** Imports
# ********************************************************
use strict;
use warnings;
use Carp;
use Device::SerialPort qw(:PARAM :STAT 0.07);
use Time::HiRes        qw (sleep);
use Time::Local        qw(timelocal); 



# ********************************************************
# *** Package Definition
# ********************************************************
require Exporter;
use vars qw (@EXPORT @EXPORT_OK @ISA);
@ISA       = qw (Exporter);
@EXPORT    = qw (ws2500_GetTime ws2500_GetStatus ws2500_GetDataset ws2500_NextDataset);
@EXPORT_OK = qw (ws2500_FirstDataset ws2500_SetDebug ws2500_InterfaceInit ws2500_GetDatasetBulk);
 


# ********************************************************
# *** Prototypes and global variables 
# ********************************************************
sub printhex              ($);
sub send_Command;
sub read_Response         ($;$);
sub init_Interface        ($);
sub close_Interface       ();
sub ws2500_GetTime        ($;$);
sub ws2500_GetStatus      ($;$);
sub ws2500_GetDataset;
sub ws2500_GetDatasetBulk ($;$;$);
sub ws2500_NextDataset;
sub ws2500_FirstDataset   ($);
sub ws2500_SetDebug       ($);
sub ws2500_InterfaceTest  ($);
sub ws2500_InterfaceInit  ($;$);

our %data;
%data = ('debug'=>0, 'maxrepeat'=>10,
	 'commands'=>{'ACTIVATE'=>'0', 'DCF'=>'1', 'NEXTSET'=>'2', 'FIRSTSET'=>'3', 'GETSET'=>'4', 'STATUS'=>'5',
	              'INTERFACETEST'=>'CTST', 'INTERFACEINIT'=>'D'},
	 'markers'=>{'SOH'=>"\x01", 'STX'=>"\x02", 'ETX'=>"\x03", 'EOT'=>"\x04", 
	             'ENQ'=>"\x05", 'ACK'=>"\x06", 
		     'DLE'=>"\x10", 'DC2'=>"\x12", 'DC3'=>"\x13",
		     'NAK'=>"\x15"});
our $VERSION = "0.99";



# ********************************************************
# *** Internal package routines 
# ********************************************************

# Returns a string in the form 2A E3 <STX>
# The special markers used in this interface (like STX=02) are replaced by
# the proper identifier. Only used by the debug messages.
# Params: data    The message to print
# Return: string  A string in the format described above
sub printhex ($) {
	my $data = shift;
	my $result = '';

	return "<no data>" if $data eq '';

	for (my $x=0;$x<length($data);$x++) { 
		my $char = substr($data,$x,1);
		my $printed = 0;

		foreach (keys %{$data{'markers'}}) {
			if ($char eq $data{'markers'}->{$_} and !$printed) {
				$result.=sprintf("<%s> ",$_);
				$printed=1;
			}
		}
		$result.=sprintf("%02X ",ord($char)) unless $printed;
	}

	return $result;
}

# Sends a command to the interface
# This subroutine only encodes and sends a message, it does not care wether
# the sent message has been received/acknowledged or not
# Params: token  A command from $data{'commands'}
#         param  An optional parameter containing additional data
# Return: 1      Always true
sub send_Command {
	my $token = shift;
	my ($checksum,$message,$command,$param);
	
	# Is this a valid command, when not die as this is an internal error
	die "Unknown command '$token'" unless exists $data{'commands'}->{$token};
	$param='';
	$param = shift if scalar @_;
	$command = $data{'commands'}->{$token}.$param;

	# Checksum is negative sum of command value, Bit 7 always set
	foreach (split //, $command) { $checksum+=ord($_); }
	$checksum = (0x100-($checksum & 0xFF)) | 0x80;
	
	# Build message and write to port

lib/Device/WS2500PC.pm  view on Meta::CPAN

	my %firstdataset;

	for (my $x=0;$x<$bulkcount;$x++) {
		if ($x==0) {
			# Request first dataset
			# As we supply the 'noclose' param the connection to the interface stays
			# open an we can request additional datasets without reestablishing the connection
			my $res = ws2500_GetDataset ($port,\%firstdataset,'next','noclose');
			# Check for errors
			if ($res and $firstdataset{'valid'} and $firstdataset{'dataset'}->{'status'} eq 'dataset') {
				push @bulkdata, $firstdataset{'dataset'};
			} else {
				last;
			}
		} else {
			# Further datasets, use the firstdataset as base
			my %result = %firstdataset;
			delete $result{'dataset'};
			my $res = ws2500_GetDataset ($port,\%result,'next','noinit');
			# Check for errors
			if ($res and $result{'valid'} and $result{'dataset'}->{'status'} eq 'dataset') {
				push @bulkdata, $result{'dataset'};
			} else {
				$firstdataset{'valid'} = $result{'valid'};	
				last;
			}
		}
	}
	# Prepare the result
	$$result{'valid'}     = $firstdataset{'valid'};
	$$result{'interface'} = $firstdataset{'interface'};
	$$result{'sensors'}   = $firstdataset{'sensors'};
	# Save the bulkdata
	$$result{'bulk'} = \@bulkdata;
	$$result{'bulkcount'} = scalar @bulkdata;

	close_Interface;

	return 1;
}


# Test Interface
# This function does not work and is not properly documented. See inline comments below
# Params: port  The port to use, e.g. /dev/ttyS0
# Return: 0     Always false, as it does not work
sub ws2500_InterfaceTest ($) {
	my $port = shift;
	my %response;
	my $valid = 0;

	return 0;

	# This doesn't seem to work. Acoording to the docu we have to send either
	# 'C' or 'CTST'. However both variants fail, and there is either no data
	# received at all, or gibberish. Furthermore the interface is not reset.
	# If anyone has a clear documentation how to activate this (and what to
	# to with it), please send them.
#	return 0 unless init_Interface ($port);
#	for (my $x=0;$x<$data{'maxrepeat'};$x++) {
#		send_Command ('INTERFACETEST');
#		sleep (0.04);
#		read_Response (1,\%response);
#		if ($response{'ok'} and $response{'message'} eq $data{'markers'}->{'ACK'}) {
#			$valid=1;
#			last;
#		}
#	}
#	close_Interface;
#
#	return 1 if $valid; 
#	return 0;
}

# Initialize the interface we new data
# Params: port  The port to sent the data, e.g. /dev/ttyS0
#         data  A hash-reference containing the configuration, see below
# Return: 0|1   True upon success, else False
# The configuration-hash must contain following keys:
# {first}        Minutes to wait after init to resume normal operation, 0..63 minutes
# {interval}     The interval in minutes to record data, 2..63 minutes
# {addr-rain}    The address of the rain sensor, 0..7
# {addr-wind}    The address of the wind sensor, 0..7
# {addr-inside}  The address of the inside sensor, 0..7
# {addr-light}   The address of the light sensor, 0..7
# {version}      The protocal version to use: 1 (V1.1) or 2 (V1.2)
sub ws2500_InterfaceInit ($;$) {
	my $port = shift;
	my $data = shift;
	my %response;
	my $valid = 0;
	my $message;

	# {'first'=>12,'interval'=>3,'addr-rain'=>7,'addr-wind'=>7,'addr-inside'=>7,'addr-ligth'=>7,'version'});

	# Prepare the message (4 Bytes)
	# First some checks if the data is correct
	foreach my $token (qw (first interval addr-rain addr-wind addr-inside addr-light version)) {
		croak "Token '$token' missing in configuration hash" unless exists $$data{$token};
		croak "Token '$token' is not a number ('$$data{'$token'}') " unless $$data{$token}=~ /^\d+$/;
	}
	# Some sanity checks 
	croak "First interval 'first' must be between 0 and 63"        if $$data{'first'}<0 or $$data{'first'}>63;
	croak "Recording interval 'interval' must be between 2 and 63" if $$data{'interval'}<2 or $$data{'interval'}>63;
	foreach my $token (qw (addr-rain addr-wind addr-inside addr-light)) {
		croak "Sensor address for '$token' must be between 0 and 7" if $$data{$token}<0 or $$data{$token}>7;
	}
	croak "Version must be either 1 (V1.1) or 2 (V1.2)" if $$data{'version'}<1 or $$data{'version'}>2;

	# Put everything together
	my $addr1 = $$data{'addr-rain'} + ($$data{'addr-wind'} << 4) + 0x80;
	$addr1|=0x8 if $$data{'version'}==1;
	my $addr2 = $$data{'addr-light'} + ($$data{'addr-inside'} << 4) + 0x80;
	# Now build the message
	$message = chr($$data{'first'}).chr($$data{'interval'}).chr($addr1).chr($addr2);

	# Send the command
	return 0 unless init_Interface ($port);
	for (my $x=0;$x<$data{'maxrepeat'};$x++) {
		send_Command ('INTERFACEINIT',$message);
		read_Response (1,\%response);



( run in 0.541 second using v1.01-cache-2.11-cpan-39bf76dae61 )