#!/usr/bin/perl use Time::HiRes qw(usleep); use FindBin; # find the script's directory use lib $FindBin::Bin; # add that directory to the library path use xPL::common; ################################################################################ # constants # $vendor_id = 'dspc'; # from xplproject.org $device_id = 'meteo'; # max 8 chars $class_id = 'meteo'; # max 8 chars $separator = '-' x 80; $indent = ' ' x 2; #------------------------------------------------------------------------------- # global variables # my $last_time = ''; my %configuration; $configuration{'sampling_period'} = 10; $configuration{'i2c_write_command'} = '/usr/sbin/i2cset'; $configuration{'i2c_read_command'} = '/usr/sbin/i2cget'; ################################################################################ # Input arguments # use Getopt::Std; my %opts; getopts('hvp:n:t:w:s:', \%opts); die("\n". "Usage: $0\n". "\n". "Parameters:\n". "${indent}-h display this help message\n". "${indent}-v verbose\n". "${indent}-p port the base UDP port\n". "${indent}-n id the instance id (max. 16 chars)\n". "${indent}-t mins the heartbeat interval in minutes\n". "${indent}-w secs the startup sleep interval\n". "${indent}-s secs the sampling period in seconds\n". "\n". "Sends the temperature at a fixed rate.\n". "\n". "More information with: perldoc $0\n". "\n". "" ) if ($opts{h}); my $verbose = $opts{v}; my $client_base_port = $opts{'p'} || 50000; my $startup_sleep_time = $opts{'w'} || 0; my $instance_id = $opts{'n'} || xpl_build_automatic_instance_id; my $heartbeat_interval = $opts{'t'} || 5; $configuration{'sampling_period'} = $opts{'s'} || $configuration{'sampling_period'}; ################################################################################ # Internal functions # #------------------------------------------------------------------------------- # Calculate the difference between now and the last sampling time # sub elapsed_time { my ($last_time) = @_; # get time information my ($second, $minute, $hour) = (localtime(time))[0, 1, 2]; my $time = $second + 60* ($minute + 60*$hour); # print "time in seconds: $time\n"; # calculate difference my $difference = $time - $last_time; if ($difference < 0) { $difference = $difference + 60 * 60 * 24; } return($time, $difference) } #------------------------------------------------------------------------------- # Read word from I2C # sub I2C_read_byte { my ($chip_address, $register_address, $configuration_ref) = @_; # build hex values my $chip_address_hex = sprintf('0x%02X', $chip_address); my $register_address_hex = sprintf('0x%02X', $register_address); # send command my $read_command = "$$configuration_ref{'i2c_read_command'} -y 1"; $read_command .= " $chip_address_hex"; $read_command .= " $register_address_hex"; #print "$read_command\n"; my $value = hex(`$read_command`); return($value); } #------------------------------------------------------------------------------- # Read word from I2C # sub I2C_read_word { my ($chip_address, $register_address, $big_endian, $configuration_ref) = @_; # build hex values my $chip_address_hex = sprintf('0x%02X', $chip_address); my $register_address_hex = sprintf('0x%02X', $register_address); # send command my $read_command = "$$configuration_ref{'i2c_read_command'} -y 1"; $read_command .= " $chip_address_hex"; $read_command .= " $register_address_hex"; $read_command .= " w"; #print "$read_command\n"; my $value_little_endian = `$read_command`; # build value chomp($value_little_endian); $value_little_endian =~ s/\A0x//; my $value = hex($value_little_endian); if ($big_endian != 0) { my $value_big_endian = substr($value_little_endian, 2, 2) . substr($value_little_endian, 0, 2); #print "$value_little_endian -> $value_big_endian\n"; $value = hex($value_big_endian); } return($value); } #------------------------------------------------------------------------------- # Write byte to I2C # sub I2C_write_byte { my ($chip_address, $register_address, $value, $configuration_ref) = @_; # build hex values my $chip_address_hex = sprintf('0x%02X', $chip_address); my $register_address_hex = sprintf('0x%02X', $register_address); my $value_hex = sprintf('0x%02X', $value); # send command my $write_command = "$$configuration_ref{'i2c_write_command'} -y 1"; $write_command .= " $chip_address_hex"; $write_command .= " $register_address_hex"; $write_command .= " $value_hex"; #print "$write_command\n"; `$write_command`; } #------------------------------------------------------------------------------- # transform 8 bit unsigned to signed # sub unsigned_to_signed_byte { my ($unsigned_value) = @_; # transform my $signed_value = unpack('c', pack('C', $unsigned_value)); return($signed_value); } #------------------------------------------------------------------------------- # transform 16 bit unsigned to signed # sub unsigned_to_signed_word { my ($unsigned_value) = @_; # transform my $signed_value = unpack('s', pack('S', $unsigned_value)); return($signed_value); } #------------------------------------------------------------------------------- # Get light measure from TSL2561 # sub get_light_intensity { my ($configuration_ref) = @_; my $big_endian = 0; my $chip_address = hex('39'); my $control_register_address = hex('80'); my $control_power_off = hex('00'); my $control_power_on = hex('03'); my $led0_register_address = hex('8C'); my $led1_register_address = hex('8E'); # power sensor on I2C_write_byte( $chip_address, $control_register_address, $control_power_on, $configuration_ref ); # get LED sensor values my $led0_value = I2C_read_word( $chip_address, $led0_register_address, $big_endian, $configuration_ref ); my $led1_value = I2C_read_word( $chip_address, $led1_register_address, $big_endian, $configuration_ref ); #print "$led0_value, $led1_value\n"; # calculate intensity my $intensity = 0; if ($led0_value != 0) { my $ratio = $led1_value / $led0_value; #print "$ratio\n"; if ($ratio <= 0.52) { $intensity = 0.0315 * $led0_value - 0.0593 * $led0_value * $ratio**1.4; } elsif ($ratio <= 0.65) { $intensity = 0.0229 * $led0_value - 0.0291 * $led1_value; } elsif ($ratio <= 0.80) { $intensity = 0.0157 * $led0_value - 0.0180 * $led1_value; } elsif ($ratio <= 1.30) { $intensity = 0.00338 * $led0_value - 0.00260 * $led1_value; } } return($intensity) } #------------------------------------------------------------------------------- # Get pressure, temperature and humidity from BME208 # sub get_station_quantities { my ($configuration_ref) = @_; my $chip_address = hex('76'); my $status_register_address = hex('F3'); my $humidity_control_register_address = hex('F2'); my $humidity_no_oversampling = hex('01'); my $humidity_measure_register_address = hex('F4'); my $measure_no_oversampling_forced_mode = hex('25'); my $pressure_register_address = hex('F7'); my $temperature_register_address = hex('FA'); my $humidity_register_address = hex('FD'); my $temperature_compensation_base_address = hex('88'); my $pressure_compensation_base_address = hex('8E'); my $humidity_compensation_base_address1 = hex('A1'); my $humidity_compensation_base_address2 = hex('E1'); # setup measurement I2C_write_byte( $chip_address, $humidity_control_register_address, $humidity_no_oversampling, $configuration_ref ); I2C_write_byte( $chip_address, $humidity_measure_register_address, $measure_no_oversampling_forced_mode, $configuration_ref ); my $status = 1; while ($status != 0) { usleep(10E3); $status = I2C_read_byte( $chip_address, $status_register_address, $configuration_ref ); #print "status : $status\n"; } # get temperature value my $big_endian = 1; my $temperature_readout = I2C_read_word( $chip_address, $temperature_register_address, $big_endian, $configuration_ref ); $temperature_readout = $temperature_readout * 2**4 + I2C_read_byte( $chip_address, $temperature_register_address+2, $big_endian, $configuration_ref ) / 2**4; #print sprintf('%06X', $temperature_readout) . "\n"; #print "temperature raw : $temperature_readout\n"; # get pressure value my $pressure_readout = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_register_address, $big_endian, $configuration_ref )); $pressure_readout = 2**4 * $pressure_readout + unsigned_to_signed_byte(I2C_read_byte( $chip_address, $pressure_register_address+2, $big_endian, $configuration_ref )) / 2**4; #print "pressure raw : $pressure_readout\n"; # get humidity value my $humidity_readout = I2C_read_word( $chip_address, $humidity_register_address, $big_endian, $configuration_ref ); #print "humidity raw : $humidity_readout\n"; # calculate temperature my $big_endian = 0; my $temp_c1 = I2C_read_word( $chip_address, $temperature_compensation_base_address+0, $big_endian, $configuration_ref ); my $temp_c2 = unsigned_to_signed_word(I2C_read_word( $chip_address, $temperature_compensation_base_address+2, $big_endian, $configuration_ref )); my $temp_c3 = unsigned_to_signed_word(I2C_read_word( $chip_address, $temperature_compensation_base_address+4, $big_endian, $configuration_ref )); #print "temp_c1 : $temp_c1\n"; #print "temp_c2 : $temp_c2\n"; #print "temp_c3 : $temp_c3\n"; # # Returns temperature in DegC, resolution is 0.01 DegC. # Output value of “5123” equals 51.23 DegC. # t_fine carries fine temperature as global value # # var1 = ((((adc_T>>3 - (dig_T1<<1))) * (dig_T2)) >> 11; # var2 = (((((adc_T>>4) - (dig_T1)) * ((adc_T>>4) - (dig_T1))) >> 12) * # (dig_T3)) >> 14; t_fine = var1 + var2; # T =(t_fine*5+128)>>8; # my $var1 = ( ($temperature_readout / 2**3 - $temp_c1 * 2) * $temp_c2 ) / 2**11; #print "var1 : $var1\n"; my $var2 = ($temperature_readout / 2**4 - $temp_c1)**2 / 2**12 * $temp_c3 / 2**14; #print "var2 : $var2\n"; my $t_fine = $var1 + $var2; my $temperature = ($t_fine * 5 + 128) / 2**8 / 1E2; #print "temperature : $temperature\n"; # calculate pressure my $press_c1 = I2C_read_word( $chip_address, $pressure_compensation_base_address+0, $big_endian, $configuration_ref ); my $press_c2 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+2, $big_endian, $configuration_ref )); my $press_c3 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+4, $big_endian, $configuration_ref )); my $press_c4 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+6, $big_endian, $configuration_ref )); my $press_c5 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+8, $big_endian, $configuration_ref )); my $press_c6 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+10, $big_endian, $configuration_ref )); my $press_c7 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+12, $big_endian, $configuration_ref )); my $press_c8 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+14, $big_endian, $configuration_ref )); my $press_c9 = unsigned_to_signed_word(I2C_read_word( $chip_address, $pressure_compensation_base_address+16, $big_endian, $configuration_ref )); #print "press_c1 : " . sprintf('%04X', $press_c1) . " $press_c1\n"; #print "press_c2 : " . sprintf('%04X', $press_c2) . " $press_c2\n"; #print "press_c3 : " . sprintf('%04X', $press_c3) . " $press_c3\n"; #print "press_c4 : " . sprintf('%04X', $press_c4) . " $press_c4\n"; #print "press_c5 : " . sprintf('%04X', $press_c5) . " $press_c5\n"; #print "press_c6 : " . sprintf('%04X', $press_c6) . " $press_c6\n"; #print "press_c7 : " . sprintf('%04X', $press_c7) . " $press_c7\n"; #print "press_c8 : " . sprintf('%04X', $press_c8) . " $press_c8\n"; #print "press_c9 : " . sprintf('%04X', $press_c9) . " $press_c9\n"; # # Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format # (24 integer bits and 8 fractional bits). # Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa # # var1 = (t_fine) – 128000; # var2 = var1 * var1 * dig_P6; # var2 = var2 + ((var1*dig_P5)<<17); # var2 = var2 + ((dig_P4)<<35); # var1 = ((var1 * var1 * dig_P3)>>8) + ((var1 * dig_P2)<<12); # var1 = ((((1)<<47)+var1))*(dig_P1)>>33; # if (var1 == 0) # { # return 0; // avoid exception caused by division by zero } # p = 1048576-adc_P; # p = (((p<<31)-var2)*3125)/var1; # var1 = ((dig_P9) * (p>>13) * (p>>13)) >> 25; # var2 = ((dig_P8) * p) >> 19; # p = ((p + var1 + var2) >> 8) + ((dig_P7)<<4); # my $pressure = 0; my $var1 = $t_fine - 128000; my $var2 = $var1**2 * $press_c6; $var2 = $var2 + ($var1 * $press_c5) * 2**17 + $press_c4 * 2**35; $var1 = $var1**2 * $press_c3 / 2**8 + $var1 * $press_c2 * 2**12; $var1 = (2**47 + $var1) * $press_c1 / 2**33; if ($var1 != 0) { $pressure = 1048576 - $pressure_readout; $pressure = ($pressure * 2**31 - $var2) * 3125 / $var1; $var1 = $press_c9 * ($pressure / 2**13)**2 / 2**25; $var2 = $press_c8 * $pressure / 2**19; $pressure = ($pressure + $var1 + $var2) / 2**8 + $press_c7 * 2**4; $pressure = $pressure / 2**8; } #print "pressure : $pressure\n"; # calculate humidity my $humid_c1 = I2C_read_byte( $chip_address, $humidity_compensation_base_address1, $configuration_ref ); my $humid_c2 = unsigned_to_signed_word(I2C_read_word( $chip_address, $humidity_compensation_base_address2+0, $big_endian, $configuration_ref )); my $humid_c3 = I2C_read_byte( $chip_address, $humidity_compensation_base_address2+2, $big_endian, $configuration_ref ); my $humid_c4 = I2C_read_byte( $chip_address, $humidity_compensation_base_address2+3, $configuration_ref ); $humid_c4 = $humid_c4 * 2**4 + I2C_read_byte( $chip_address, $humidity_compensation_base_address2+4, $configuration_ref ) % 2**4; my $humid_c5 = int( I2C_read_byte( $chip_address, $humidity_compensation_base_address2+4, $configuration_ref ) / 2**4); $humid_c5 = $humid_c5 + 2**4 * I2C_read_byte( $chip_address, $humidity_compensation_base_address2+5, $configuration_ref ); my $humid_c6 = unsigned_to_signed_byte(I2C_read_byte( $chip_address, $humidity_compensation_base_address2+6, $configuration_ref )); #print "humid_c1 : " . sprintf('%04X', $humid_c1) . " $humid_c1\n"; #print "humid_c2 : " . sprintf('%04X', $humid_c2) . " $humid_c2\n"; #print "humid_c3 : " . sprintf('%04X', $humid_c3) . " $humid_c3\n"; #print "humid_c4 : " . sprintf('%04X', $humid_c4) . " $humid_c4\n"; #print "humid_c5 : " . sprintf('%04X', $humid_c5) . " $humid_c5\n"; #print "humid_c6 : " . sprintf('%04X', $humid_c6) . " $humid_c6\n"; my $var1_max = 419430400; my $humidity = 0; # # Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format # (22 integer and 10 fractional bits). # Output value of “47445” represents 47445/1024 = 46.333 %RH # # v_x1_u32r = (t_fine – ((BME280_S32_t)76800)); # v_x1_u32r = (((((adc_H << 14) - ((dig_H4) << 20) - ((dig_H5) * v_x1_u32r)) + # (16384)) >> 15) * (((((((v_x1_u32r * (dig_H6)) >> 10) * (((v_x1_u32r * # (dig_H3)) >> 11) + (32768))) >> 10) + (2097152)) * (dig_H2) + 8192) >> 14)); # # $var1 = (((($humidity_readout * 2**14 - ($humid_c4 * 2**20) - # ($humid_c5 * $var1)) + 16384) / 2**15) * ((((((($var1 * $humid_c6) / 2**10) * # ((($var1 * $humid_c3) / 2**11) + 32768)) / 2**10) + 2097152) * $humid_c2 + # 8192) / 2**14)); # # my $var1 = $t_fine - 76800; # my $var2 = $humidity_readout * 2**14 - $humid_c4 * 2**20 - $humid_c5 * $var1 + 16384; # my $var3 = $var1 * $humid_c6 / 2**10 * ($var1 * $humid_c3 / 2**11 + 32768); # $var3 = ($var3 / 2**10 + 2097152) * $humid_c2 + 8192; # $var1 = $var2 / 2**15 * $var3 / 2**14; # if ($var1 > $var1_max) { # $humidity = $var1_max / 2**10; # } # elsif ($var1 > 0) { # $humidity = $var1 / 2**10; # } $var1 = $t_fine - 76800; my $var2 = $humidity_readout - $humid_c4 * 2**6 + $humid_c5 / 2**14 * $var1; my $var3 = 1.0 + $humid_c3 / 2**26 * $var1; $var3 = 1.0 + $humid_c6 / 2**26 * $var1 * $var3; $var1 = $var2 * $humid_c2 / 2**16 * $var3; $var1 = $var1 * (1.0 - $humid_c1 * $var1 / 2**19); if ($var1 > 100) { $var1 = 100; } elsif ($var1 > 0) { $humidity = $var1; } #print "humidity : $humidity\n"; return($temperature, $pressure, $humidity) } ################################################################################ # Main script # ################################################################################ # Catch control-C interrupt # $SIG{INT} = sub{ $xpl_end++ }; ################################################################################ # Main script # sleep($startup_sleep_time); # xPL parameters my $xpl_id = xpl_build_id($vendor_id, $device_id, $instance_id); my $xpl_ip = xpl_find_ip; # create xPL socket my ($client_port, $xpl_socket) = xpl_open_socket($xpl_port, $client_base_port); # display working parameters if ($verbose == 1) { system("clear"); print("$separator\n"); print("Starting xPL front application reporter on port $client_port.\n"); print($indent . "class id: $class_id\n"); print($indent . "instance id: $instance_id\n"); print("\n"); } #=============================================================================== # Main loop # my $timeout = 1; my $last_heartbeat_time = 0; my $last_time = ''; while ( (defined($xpl_socket)) && ($xpl_end == 0) ) { # check time and send heartbeat $last_heartbeat_time = xpl_send_heartbeat( $xpl_socket, $xpl_id, $xpl_ip, $client_port, $heartbeat_interval, $last_heartbeat_time ); # get new time sleep($timeout); my ($time, $elapsed_time) = elapsed_time($last_time); # update front application status if ($elapsed_time >= $configuration{'sampling_period'} ) { #print "elapsed time is $elapsed_time\n"; # get light intensity my $intensity = get_light_intensity(\%configuration); # get pressure, temperature and humidity my ($temperature, $pressure, $humidity) = get_station_quantities(\%configuration); if ($verbose > 0) { print 'light intensity : ' . sprintf('%.1f', $intensity) . " [lux]\n"; print 'temperature : ' . sprintf('%.1f', $temperature) . " [C]\n"; print 'pressure : ' . sprintf('%d', $pressure/100) . " [hPa]\n"; print 'humidity : ' . sprintf('%.1f', $humidity) . " [%]\n"; print("\n"); } # send xPL message xpl_send_message( $xpl_socket, $xpl_port, 'xpl-stat', $xpl_id, '*', "$class_id.sample", ( 'light' => int($intensity + 0.5), 'temperature' => sprintf('%.1f', $temperature), 'pressure' => int($pressure/100 + 0.5), 'humidity' => int($humidity + 0.5) ) ); $last_time = $time; } } xpl_disconnect($xpl_socket, $xpl_id, $xpl_ip, $client_port); ################################################################################ # Documentation (access it with: perldoc ) # __END__ =head1 NAME xpl-pi-meteo.pl - Sends the temperature at a fixed rate =head1 SYNOPSIS xpl-pi-meteo.pl [options] =head1 DESCRIPTION This xPL client sends a C message status every minute. The message body looks like: C. =head1 OPTIONS =over 8 =item B<-h> Display a help message. =item B<-v> Be verbose. =item B<-p port> Specify the base port from which the client searches for a free port. If not specified, the client will take a default value. =item B<-n id> Specify the instance id (name). The id is limited to 16 characters. If not specified, it is constructed from the host name. =item B<-t mins> Specify the number of minutes between two heartbeat messages. =item B<-w secs> Specify the number of seconds before sending the first heartbeat. This allows to start the client after the hub, thus eliminating an prospective startup delay of one heartbeat interval. =item B<-s secs> Specify the the sampling period in seconds. =back =head1 TEST Make sure you have an C running on the machine. Start C in a terminal window. Start C in another terminal window. =head1 AUTHOR Francois Corthay, DSPC =head1 VERSION 1.0, 2017 =cut