package APIsigclient; # # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (c) Rainer Gümpelein, TeilRad GmbH # #Client for sig # #380116b5-0522-43da-ab66-477744a731a3 # #use lib qw(/var/www/copri-bike/shareeapp-sx/src); use warnings; use strict; use POSIX; use Exporter; our @ISA = qw (Exporter); #use POSIX; use CGI; use JSON; use LWP::UserAgent; use DateTime; use Time::Piece; use Scalar::Util qw(looks_like_number); use Config::General; use Lib::Config; use Mod::DBtank; use Mod::Basework; use Mod::APIfunc; use Data::Dumper; my $q = new CGI; my $json = JSON->new->allow_nonref; my $cf = new Config; my $dbt = new DBtank; my $apif = new APIfunc; my $bw = new Basework; sub new { my $class = shift; my $self = {}; bless($self,$class); return $self; } my $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime; my $api_file = "/var/www/copri4/shareeconf/apikeys.cfg"; my $aconf = Config::General->new($api_file); my %apikeyconf = $aconf->getall; my $ua = LWP::UserAgent->new; $ua->agent("sharee sigclient"); my $size = $ua->max_size; my $bytes = 100000; $ua->max_size( $bytes ); $ua->default_header( 'x-api-key' => $apikeyconf{sigo}->{api_key} ); #will be called on bikes_available sub sig_available { my $self = shift; my $q = shift; my $varenv = shift || {}; my $ctadr = shift || {}; my $response_in = {}; my $dbh = ""; my $owner = 169; my $authed = 0; my $ctpos = { c_id => 0 }; my $show_dialog = {}; if(ref($ctadr) eq "HASH" && $ctadr->{c_id} && $ctadr->{c_id} > 0){ $authed = 1; ($ctpos,$show_dialog) = $apif->rental_to_feedback($varenv,$ctadr); } my ($bike_group,$bike_node,$user_tour,$tariff_content,$adrtarif_hash) = $apif->fetch_tariff($varenv->{dbname},$ctadr,$q->param('authcookie')); my $hotline_hash = { table => "contentuser", fetch => "one", template_id => 197, c_id => "1", }; my $hotline_data = $dbt->fetch_record($dbh,$hotline_hash); my $td_template = $dbt->rental_description_template(); open(FILE,">>$varenv->{logdir}/APIsigclient.log"); print FILE "\n0. *** $now_dt 'sig_available' ctadr: $ctadr->{c_id}\n"; #my $endpoint = "https://sigo.dev.sigo.green/api/v1/bikes"; my $endpoint = "$dbt->{operator}->{$varenv->{dbname}}->{endpoint}/bikes"; my $response_out = {}; my $return2copri = {}; my $rest_json = ""; (my $ret_json, my $ret_status) = $self->get_sig("$endpoint",$rest_json); eval { $response_in = decode_json($ret_json); $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime; print FILE "<--- $now_dt station_and_bikes response_in with status_line: $ret_status:\n"; #print FILE Dumper($response_in) . "\n"; if(ref($response_in) eq "HASH"){ foreach my $resp (@{ $response_in->{items} }) { print FILE "response_in loop $dbt->{operator}->{$varenv->{dbname}}->{operatorApp}\n" . $q->param('request') . "\n" . Dumper($resp) . "\n"; if(ref($resp->{site}) eq "HASH" && $dbt->{operator}->{$varenv->{dbname}}->{operatorApp} && $q->param('request') eq "stations_available"){ #station (mainly using sigojson site object) my $station = "SX$resp->{site}->{id}"; print FILE "---> Station: $station| bike_group: @{$bike_group}[0])\n"; if($station && looks_like_number($resp->{site}->{id}) && ref($bike_group) eq "ARRAY" && @{$bike_group}[0]){ print FILE "Station: $station\n\n"; $response_out->{$station}->{station} = "$station"; $response_out->{$station}->{authed} = "$authed"; $response_out->{$station}->{uri_operator} = "$dbt->{operator}->{$varenv->{dbname}}->{operatorApp}"; #FIXME charset encoding $response_out->{$station}->{description} = ""; #$response_out->{$station}->{description} = "$resp->{site}->{address}" || ""; $response_out->{$station}->{description} = Encode::encode('utf-8', Encode::decode('iso-8859-1',$resp->{site}->{address})) || ""; $response_out->{$station}->{station_group} = ["SX300101"];#Lastenrad alias type_id $response_out->{$station}->{gps}->{latitude} = "$resp->{site}->{lat}" || ""; $response_out->{$station}->{gps}->{longitude} = "$resp->{site}->{lon}" || ""; $response_out->{$station}->{gps_radius} = "75"; #Other than Active status, should not be used to display information to a customer $response_out->{$station}->{state} = "defect"; if(uc($resp->{site}->{status}) =~ /ACTIVE|PRIVATE/i){ $response_out->{$station}->{state} = "available"; }elsif(uc($resp->{site}->{status}) eq "MAINTENANCE"){ $response_out->{$station}->{state} = "maintenance"; } $response_out->{$station}->{operator_data} = { "operator_name" => "", "operator_hours" => "", "operator_color" => "", "operator_logo" => "", "operator_phone" => "", "operator_email" => "", }; $response_out->{$station}->{operator_data}->{operator_name} = Encode::encode('utf-8', Encode::decode('iso-8859-1',$hotline_data->{txt01})) if($hotline_data->{txt01}); $response_out->{$station}->{operator_data}->{operator_hours} = Encode::encode('utf-8', Encode::decode('iso-8859-1',$hotline_data->{txt84})) if($hotline_data->{txt84}); $response_out->{$station}->{operator_data}->{operator_color} = $hotline_data->{txt85} if($hotline_data->{txt85}); $response_out->{$station}->{operator_data}->{operator_logo} = $hotline_data->{img01} if($hotline_data->{img01}); $response_out->{$station}->{operator_data}->{operator_phone} = $hotline_data->{txt07} if($hotline_data->{txt07}); $response_out->{$station}->{operator_data}->{operator_email} = $hotline_data->{txt08} if($hotline_data->{txt08}); #just like caching $return2copri->{$station}->{barcode} = $1 if($response_out->{$station}->{station} =~ /(\d+)/);#new on station context $return2copri->{$station}->{int04} = $1 if($response_out->{$station}->{station} =~ /(\d+)/); $return2copri->{$station}->{int06} = $1 if($response_out->{$station}->{gps_radius} =~ /(\d+)/); $return2copri->{$station}->{txt01} = "$response_out->{$station}->{description}"; $return2copri->{$station}->{txt06} = "$response_out->{$station}->{gps}->{latitude},$response_out->{$station}->{gps}->{longitude}"; $return2copri->{$station}->{txt24} = "300102";#node.main_id $return2copri->{$station}->{txt25} = "300101";#node_type_id while (my ($key, $value) = each %{ $dbt->{copri_conf}->{station_state} }) { if($response_out->{$station}->{state} eq $value){ $return2copri->{$station}->{int10} = $key; } } #print FILE "response_out:" . Dumper($response_out->{$station}) . "\n"; #print FILE "return2copri:" . Dumper($return2copri->{$station}) . "\n"; delete $response_out->{$station} if(uc($resp->{site}->{status}) !~ /ACTIVE|PRIVATE/i || !$resp->{site}->{lat} || !$resp->{site}->{lon}); } }#end stations_available if(ref($resp->{site}) eq "HASH" && $dbt->{operator}->{$varenv->{dbname}}->{operatorApp} && $q->param('request') eq "bikes_available"){ #bike (mainly using sigojson state object) #my $bike = "SX$resp->{mobile_bike_id}"; my $bike = $q->escapeHTML($resp->{license_plate}) || ""; my $bike_id = $bike; $bike_id =~ s/S[1-9]X/SX/; $bike_id = $1 if($bike_id =~ /(\d+)/); print FILE "bike-data $bike_id | $resp->{site}->{id} | @{$bike_group}[0]\n"; if($bike && looks_like_number($bike_id) && looks_like_number($resp->{site}->{id}) && ref($bike_group) eq "ARRAY" && @{$bike_group}[0]){ print FILE "Bike: $bike\n\n"; if($ctpos->{barcode} && $ctpos->{barcode} == $bike_id){ $response_out->{$bike}->{user_miniquery} = $show_dialog->{user_miniquery} if($show_dialog->{user_miniquery}); $response_out->{$bike}->{co2saving} = ""; if($show_dialog->{co2saving}){ $response_out->{$bike}->{co2saving} = $show_dialog->{co2saving}; } } $response_out->{$bike}->{bike} = "$bike"; $response_out->{$bike}->{authed} = "$authed"; $response_out->{$bike}->{station} = "SX$resp->{site}->{id}" || ""; $response_out->{$bike}->{uri_operator} = "$dbt->{operator}->{$varenv->{dbname}}->{operatorApp}"; $response_out->{$bike}->{description} = "E-Lastenrad"; $response_out->{$bike}->{gps}->{latitude} = "$resp->{state}->{lat}" || ""; $response_out->{$bike}->{gps}->{longitude} = "$resp->{state}->{lon}" || ""; $response_out->{$bike}->{lock_state} = "locked"; $response_out->{$bike}->{state} = "defect"; if($resp->{status} && $resp->{status} =~ /ACTIVE|PRIVATE/i){ if(uc($resp->{reservation_state}) eq "RESERVED"){ $response_out->{$bike}->{state} = "requested"; }elsif(uc($resp->{reservation_state}) eq "ACTIVE-RENTAL"){ $response_out->{$bike}->{lock_state} = "unlocked"; $response_out->{$bike}->{state} = "occupied"; }elsif(!$resp->{reservation_state}){ $response_out->{$bike}->{state} = "available"; } }elsif($resp->{status} && $resp->{status} eq "MAINTENANCE"){ $response_out->{$bike}->{state} = "maintenance"; } $response_out->{$bike}->{bike_type}->{engine}->{manufacturer} = "sigo"; my $max_bars = 5; my $current_bars = 0; $response_out->{$bike}->{bike_type}->{battery}->{charge_max_bars} = "$max_bars"; $response_out->{$bike}->{bike_type}->{battery}->{charge_current_bars} = "$current_bars"; $response_out->{$bike}->{bike_type}->{battery}->{charge_current_percent} = "0"; $response_out->{$bike}->{bike_type}->{battery}->{backend_accessible} = "1";#got it from backend $response_out->{$bike}->{bike_type}->{battery}->{hidden} = "0";#1=hide charge view if(looks_like_number($resp->{energy_level})){ $current_bars = $bw->battery_bars($max_bars,$resp->{energy_level}); $response_out->{$bike}->{bike_type}->{battery}->{charge_current_bars} = "$current_bars"; $response_out->{$bike}->{bike_type}->{battery}->{charge_current_percent} = "$resp->{energy_level}"; } $response_out->{$bike}->{system} = "sigo"; $response_out->{$bike}->{bike_group} = ["SX300101"];#Lastenrad type_id $response_out->{$bike}->{unlock_allowed} = "1"; $response_out->{$bike}->{tariff_description} = {}; $response_out->{$bike}->{rental_description} = {}; if(ref($tariff_content) eq "HASH"){ foreach my $tid (sort { $tariff_content->{$a}->{barcode} <=> $tariff_content->{$b}->{barcode} } keys (%$tariff_content)){ #deprecated $response_out->{$bike}->{tariff_description}->{name} = "$tariff_content->{$tid}->{ct_name}"; $response_out->{$bike}->{tariff_description}->{number} = "$tariff_content->{$tid}->{barcode}"; $response_out->{$bike}->{tariff_description}->{eur_per_hour} = "$tariff_content->{$tid}->{int02}" || "0"; $response_out->{$bike}->{tariff_description}->{max_eur_per_day} = "$tariff_content->{$tid}->{int17}" if($tariff_content->{$tid}->{int17}); $response_out->{$bike}->{tariff_description}->{free_hours} = "$tariff_content->{$tid}->{int16}" if($tariff_content->{$tid}->{int16}); $response_out->{$bike}->{tariff_description}->{abo_eur_per_month} = "$tariff_content->{$tid}->{int15}" if($tariff_content->{$tid}->{int15}); #new rental_description $response_out->{$bike}->{rental_description}->{name} = "$tariff_content->{$tid}->{ct_name}"; $response_out->{$bike}->{rental_description}->{id} = "$tariff_content->{$tid}->{barcode}"; $response_out->{$bike}->{rental_description}->{reserve_timerange} = "30"; $response_out->{$bike}->{rental_description}->{rental_info}->{1} = ["Tracking","Ich stimme der Speicherung (Tracking) meiner Fahrstrecke zwecks wissenschaftlicher Auswertung und Berechnung der CO2-Einsparung zu!"];#TODO if($resp->{gps_tracker_id}); #$response_out->{$bike}->{rental_description}->{rental_info}->{2} = ["AGB","Mit der Mietrad Anmietung wird folgender Betreiber AGB zugestimmt (als Demo sharee AGB)."] if($ctadr->{c_id} && ($ctadr->{c_id} == 1842 || $ctadr->{c_id} == 1843 || $ctadr->{c_id} == 5781 || $ctadr->{c_id} == 11765 || $ctadr->{c_id} == 38883)); my $i = 0; foreach my $td (sort keys (%$td_template)){ my $time_unit = ""; if($td_template->{$td}->{int35} && $tariff_content->{$tid}->{int35} && $tariff_content->{$tid}->{int35} > 0){ $tariff_content->{$tid}->{int35} =~ s/\./,/; $time_unit = $dbt->time_format($tariff_content->{$tid}->{time01}); $response_out->{$bike}->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int35}","$tariff_content->{$tid}->{int35} € / $time_unit"]; }elsif($td_template->{$td}->{int36} && $tariff_content->{$tid}->{int36} && $tariff_content->{$tid}->{int36} > 0){ $tariff_content->{$tid}->{int36} =~ s/\./,/; $time_unit = $dbt->time_format($tariff_content->{$tid}->{time01}); $response_out->{$bike}->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int36}", "$tariff_content->{$tid}->{int36} € / $time_unit"]; }elsif($td_template->{$td}->{int17} && $tariff_content->{$tid}->{int17} && $tariff_content->{$tid}->{int17} > 0){ $tariff_content->{$tid}->{int17} =~ s/\./,/; $response_out->{$bike}->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int17}","$tariff_content->{$tid}->{int17} € / Tag"]; }elsif($td_template->{$td}->{time02} && $tariff_content->{$tid}->{time02} =~ /[1-9]/){ $time_unit = $dbt->time_format($tariff_content->{$tid}->{time02}); $response_out->{$bike}->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{time02}","$time_unit / Tag"]; } }#end new rental_description } } #just like caching $return2copri->{$bike}->{int11} = 3;#system $return2copri->{$bike}->{int25} = 1;#tracking on $return2copri->{$bike}->{barcode} = $bike_id; $return2copri->{$bike}->{txt22} = $resp->{id};#sig bikeId used by rental $return2copri->{$bike}->{int04} = $1 if($response_out->{$bike}->{station} =~ /(\d+)/); $return2copri->{$bike}->{txt01} = "$response_out->{$bike}->{description}"; $return2copri->{$bike}->{int25} = "1" if($resp->{gps_tracker_id}); $return2copri->{$bike}->{txt06} = "$response_out->{$bike}->{gps}->{latitude},$response_out->{$bike}->{gps}->{longitude}"; $return2copri->{$bike}->{int19} = $1 if($response_out->{$bike}->{bike_type}->{battery}->{charge_current_percent} =~ /(\d+)/); while (my ($key, $value) = each %{ $dbt->{copri_conf}->{bike_state} }) { if($response_out->{$bike}->{state} eq $value){ $return2copri->{$bike}->{int10} = $key; } } while (my ($key, $value) = each %{ $dbt->{copri_conf}->{lock_state} }) { if($response_out->{$bike}->{lock_state} eq $value){ $return2copri->{$bike}->{int20} = $key; } } delete $response_out->{$bike} if(uc($resp->{status}) !~ /ACTIVE|PRIVATE/i || $resp->{reservation_state});#don't view not active bikes } }#end bikes_available } } }; if ($@){ print FILE "<--- failure get_bikes raw response_in with status_line: $ret_status\n" . Dumper($ret_json) . "\n"; #warn $@; print FILE "warn:" . $@ . "\n"; } print FILE "sig_available response_out from response_in\n" . Dumper($response_out) . "\n"; close(FILE); return ($response_out,$return2copri); }#end sig_available #bike smartlock unlocking sub sig_unlock { my $self = shift; my $varenv = shift || {}; my $todo = shift || ""; my $ctadr = shift || {}; my $ct_bike = shift || {}; my $ctpos = shift || {}; my $sig_bikeId = $ctpos->{txt22} || $ct_bike->{txt22}; my $dbh = ""; my $owner = 169; my $sig_book = {}; my $rows = 0; open(FILE,">>$varenv->{logdir}/APIsigclient.log"); print FILE "\n3. *** $now_dt 'sig_unlock' \n"; my $endpoint = "$dbt->{operator}->{$varenv->{dbname}}->{endpoint}/bikes/unlock/$sig_bikeId"; my $response_out = {}; my $return2copri = {}; my $rest_json = ""; my $ret_json = "failure: ret_json"; my $ret_status = "failure: ret_status"; ($ret_json, $ret_status) = $self->get_sig("$endpoint",$rest_json); eval { $sig_book = decode_json($ret_json); $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime; print FILE "<--- $now_dt sig_unlock sig_book json with status_line: $ret_status:\n" . Dumper($sig_book) . "\n"; }; if ($@){ print FILE "<--- $now_dt failure sig_unlock raw ret_json with status_line: $ret_status\n" . Dumper($ret_json) . "\n"; #warn $@; print FILE "warn:" . $@ . "\n"; } #save always API return state for documentation $sig_book->{return_state} = "$now_dt $todo: $ret_status"; if($ctpos->{c_id}){ my $rows = 0; my $update_pos = { table => "contenttranspos", mtime => "now()", owner => "169", txt25 => "$ctpos->{txt25}\n$sig_book->{return_state}", }; my $update_content = { table => "content", mtime => "now()", owner => "169", c_id => $ctpos->{cc_id}, }; $update_pos->{int20} = 2 if($ret_json eq "Bike unlocked"); $update_content->{int20} = 2 if($ret_json eq "Bike unlocked"); $rows = $dbt->update_record($dbh,$update_pos,$ctpos); print FILE "<--- rows: $rows, update_pos: $ctpos->{c_id}, with:" . Dumper($update_pos) . "\n"; if($ctpos->{cc_id} && $ret_json eq "Bike unlocked"){ $rows = $dbt->update_record($dbh,$update_content,$update_content); print FILE "<--- rows: $rows, update_content: $ctpos->{cc_id}, with:" . Dumper($update_content) . "\n"; } } print FILE "sig_unlock ret_json from response_in\n" . Dumper($ret_json) . "\n"; close(FILE); return $sig_book; } #POST resverve, rental, ... #will be called by sig_client sub sig_booking { my $self = shift; my $varenv = shift || {}; my $todo = shift || ""; my $ctadr = shift || {}; my $ct_bike = shift || {}; my $ctpos = shift || {}; my $dbh = ""; my $owner = 169; my $sig_book = {}; open(FILE,">>$varenv->{logdir}/APIsigclient.log"); print FILE "\n2. *** $now_dt 'sig_post $todo' \n"; my $endpoint = "$dbt->{operator}->{$varenv->{dbname}}->{endpoint}/"; my %json = (); #reservation start if($todo eq "reserve"){ $endpoint .= "bikes/reserve"; %json = ( bikeId => "$ct_bike->{txt22}", email => "$ctadr->{txt08}" ); } #reservation end, this sig request is still not defined. maybe end by rentalId #int10 state will be set on booking_request elsif($todo eq "reserve_end"){ $endpoint .= "bikes/reserve/end"; %json = ( rentalId => "$ctpos->{txt11}", email => "$ctadr->{txt08}" ); } #rental start #int10 state will be set on main booking_update elsif($todo eq "rental"){ $endpoint .= "rental"; my $sig_bikeId = $ctpos->{txt22} || $ct_bike->{txt22}; %json = ( bikeId => "$sig_bikeId", email => "$ctadr->{txt08}" ); } #rental end elsif($todo eq "rental_end"){ $endpoint .= "rental/end"; %json = ( rentalId => "$ctpos->{txt11}", email => "$ctadr->{txt08}" ); #keep in mind, it will return no json, just text: "Rental Ended" } #rentals running #TODO, execute it before user_bikes_occupied elsif($todo eq "rentals_running"){ $endpoint .= "rentals/running"; %json = ( email => "$ctadr->{txt08}" ); } else{ print "Failure, request $todo not defined\n"; } print FILE "---> DATA $endpoint:\n" . Dumper(\%json) . "\n"; if(ref(\%json) eq "HASH" && $json{email}){ my $rest_json = encode_json(\%json); (my $ret_json, my $ret_status) = $self->post_sig($endpoint,$rest_json); $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime; eval { $sig_book = decode_json($ret_json); print FILE "<--- $now_dt sig_booking sig_post $todo response_in with status_line: $ret_status\n" . Dumper($sig_book); #print $ret_json . "\n"; }; if ($@){ print FILE "<--- $now_dt failure sig_booking sig_post $todo raw response_in with status_line: $ret_status\n" . Dumper($ret_json) . "\n"; #warn $@; print FILE "warn:" . $@ . "\n"; } #save always API return state for documentation $sig_book->{return_state} = "$now_dt $todo: $ret_status"; } if(ref($sig_book) ne "HASH"){ $sig_book = { bikeId => "", rentalId => "", }; print FILE "<--- $now_dt failure sig_booking sig_post $todo , reset sig_book hash to empty\n"; } if($ctpos->{c_id}){ my $rows = 0; my $update_pos = { table => "contenttranspos", mtime => "now()", owner => "169", }; my $update_content = { table => "content", mtime => "now()", owner => "169", c_id => $ctpos->{cc_id}, }; #rentalId will be only on success! if($sig_book->{rentalId}){ $update_pos->{txt11} = "$sig_book->{rentalId}"; } if($todo eq "reserve" || $todo eq "rental"){ #mark it as unlocked if rentalId on rental if($todo eq "rental" && $sig_book->{rentalId}){ $update_pos->{int20} = 2; $update_content->{int20} = 2; } #without rentalId, bike will be available and unlocked! else{ $update_pos->{int10} = 1; $update_pos->{int20} = 1; $update_content->{int10} = 1; $update_content->{int20} = 1; } } #keep in mind, all other states will be done on REST, hopefully $update_pos->{txt25} = "$ctpos->{txt25}\n$sig_book->{return_state}";# if($sig_book->{return_state}); $rows = $dbt->update_record($dbh,$update_pos,$ctpos); print FILE "<--- rows: $rows, update_pos: $ctpos->{c_id}, with:" . Dumper($update_pos) . "\n"; if($ctpos->{cc_id}){ $rows = $dbt->update_record($dbh,$update_content,$update_content); print FILE "<--- rows: $rows, update_content: $ctpos->{cc_id}, with:" . Dumper($update_content) . "\n"; } } close(FILE); return $sig_book; } #main GET sub get_sig { my $self = shift; my $endpoint = shift || ""; my $rest_json = shift || ""; my $sig_request = "$endpoint"; print FILE "===> GET sig >> " . $sig_request . "\n" . $rest_json . "\n"; my $req = HTTP::Request->new(GET => "$sig_request"); $req->content_type('application/json'); $req->content($rest_json); my $res = $ua->request($req); if ($res->is_success) { #print $res->content; #print $res->status_line, "\n"; return ($res->content, $res->status_line); }else { #print $res->status_line, "\n"; return ("", $res->status_line); } } #main POST sub post_sig { my $self = shift; my $endpoint = shift || ""; my $rest_json = shift || ""; my $sig_request = "$endpoint"; print FILE "===> POST sig >> " . $sig_request . "\n" . $rest_json . "\n"; my $req = HTTP::Request->new(POST => "$sig_request"); $req->content_type('application/json'); $req->content($rest_json); my $res = $ua->request($req); if ($res->is_success) { #print $res->content; #print $res->status_line, "\n"; return ($res->content, $res->status_line); }else { #print $res->status_line, "\n"; return ("", $res->status_line); } } 1;