package APIsigoclient;
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (c) Rainer Gümpelein, TeilRad GmbH
#
#Client for sigo
#
#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 sigoclient");
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 sigo_available {
my $self = shift;
my $q = shift;
my $varenv = shift || {};
my $auth = shift || {};
my $response_in = {};
my $dbh = "";
my $owner = 169;
my $authed = 0;
my $tariff_content = {};
$authed = 1 if(ref($auth) eq "HASH" && $auth->{c_id} && $auth->{c_id} > 0);
(my $bike_group,my $user_group,$tariff_content,my $user_tour) = $apif->fetch_tariff($varenv->{dbname},$auth,$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->tariff_description2_template();
open(FILE,">>$varenv->{logdir}/APIsigo_client.log");
print FILE "\n*** $now_dt 'sigo_available' \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_sigo("$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\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}";
if($station && looks_like_number($resp->{site}->{id}) && $resp->{site}->{status} eq "ACTIVE" && $resp->{site}->{lat} && $resp->{site}->{lon}){
print FILE "Station: $station\n";
$response_out->{$station}->{station} = "$station";
$response_out->{$station}->{authed} = "$authed";
$response_out->{$station}->{uri_operator} = "$dbt->{operator}->{$varenv->{dbname}}->{operatorApp}";
$response_out->{$station}->{description} = "$resp->{site}->{address}" || "";
$response_out->{$station}->{station_group} = ["SX300102"];#E-Lastenrad
$response_out->{$station}->{gps}->{latitude} = "$resp->{site}->{lat}" || "";
$response_out->{$station}->{gps}->{longitude} = "$resp->{site}->{lon}" || "";
$response_out->{$station}->{gps_radius} = "75";
$response_out->{$station}->{state} = "defect";
if($resp->{site}->{status} eq "ACTIVE"){#?
$response_out->{$station}->{state} = "available";
}elsif($resp->{site}->{status} eq "MAINTANANCE"){
$response_out->{$station}->{state} = "maintanance";
}elsif($resp->{site}->{status} eq "OFFLINE"){
$response_out->{$station}->{state} = "defect";
}
$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";
while (my ($key, $value) = each %{ $dbt->{copri_conf}->{station_state} }) {
if($response_out->{$station}->{state} eq $value){
$return2copri->{$station}->{int10} = $key;
}
}
}
}#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+)/);
if($bike && looks_like_number($bike_id) && looks_like_number($resp->{site}->{id})){
print FILE "Bike: $bike\n";
$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}->{bike_charge} = "$resp->{energy_level}" || "0";
$response_out->{$bike}->{state} = "defect";
if($resp->{reservation_state} eq "RESERVED"){
$response_out->{$bike}->{state} = "requested";
}elsif($resp->{reservation_state} eq "ACTIVE-RENTAL"){
$response_out->{$bike}->{state} = "occupied";
}elsif(!$resp->{reservation_state}){
$response_out->{$bike}->{state} = "available";
}
#$response_out->{$bike}->{lock_state} = "undefined";#?
#$response_out->{$bike}->{lock_state} = "locked";# I think default should be unlocked
$response_out->{$bike}->{lock_state} = "unlocked";
if($resp->{state}->{locked}){
$response_out->{$bike}->{lock_state} = "locked";
}
$response_out->{$bike}->{system} = "sigo";
$response_out->{$bike}->{bike_group} = ["SX300102"];#E-Lastenrad
$response_out->{$bike}->{unlock_allowed} = "1";
$response_out->{$bike}->{tariff_description} = {};
$response_out->{$bike}->{tariff_description2} = {};
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});
$response_out->{$bike}->{tariff_description}->{operator_agb} = "Mit der Mietrad Anmietung wird folgender Betreiber AGB zugestimmt (Demo)." if($auth->{c_id} && ($auth->{c_id} == 1842 || $auth->{c_id} == 5781 || $auth->{c_id} == 22262));
#new tariff_description2
$response_out->{$bike}->{tariff_description2}->{name} = "$tariff_content->{$tid}->{ct_name}";
$response_out->{$bike}->{tariff_description2}->{number} = "$tariff_content->{$tid}->{barcode}";
$response_out->{$bike}->{tariff_description2}->{track_info} = "Ich stimme der Speicherung (Tracking) meiner Fahrstrecke zwecks wissenschaftlicher Auswertung und Berechnung der CO2-Einsparung zu!";# if($record->{$id}->{int25});
$response_out->{$bike}->{tariff_description2}->{operator_agb} = "Mit der Mietrad Anmietung wird folgender Betreiber AGB zugestimmt (als Demo sharee AGB)." if($auth->{c_id} && ($auth->{c_id} == 1842 || $auth->{c_id} == 5781 || $auth->{c_id} == 22262));
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}->{tariff_description2}->{$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}->{tariff_description2}->{$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}->{tariff_description2}->{$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}->{tariff_description2}->{$td} = ["$td_template->{$td}->{time02}","$time_unit / Tag"];
}
}#end new tariff_description2
}
}
#just like caching
$return2copri->{$bike}->{int11} = 3;
$return2copri->{$bike}->{barcode} = $bike_id;
$return2copri->{$bike}->{txt22} = $resp->{id};#sig bikeId like 380116b5-0522-43da-ab66-477744a731a3
$return2copri->{$bike}->{int04} = $1 if($response_out->{$bike}->{station} =~ /(\d+)/);
$return2copri->{$bike}->{txt01} = "$response_out->{$bike}->{description}";
$return2copri->{$bike}->{txt06} = "$response_out->{$bike}->{gps}->{latitude},$response_out->{$bike}->{gps}->{longitude}";
$return2copri->{$bike}->{int19} = $1 if($response_out->{$bike}->{bike_charge} =~ /(\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($resp->{status} ne "ACTIVE");#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 "sigo_available response_out from response_in\n" . Dumper($response_out) . "\n";
close(FILE);
return ($response_out,$return2copri);
}#end sigo_available
#POST resverve, rental, ...
sub sig_booking {
my $self = shift;
my $varenv = shift || {};
my $auth = shift || {};
my $ct_bike = shift || {};
my $ctpos = shift || {};
my $todo = shift || "";
my $dbh = "";
my $owner = 169;
my $sig_book = {};
open(FILE,">>$varenv->{logdir}/APIsigo_client.log");
print FILE "\n*** $now_dt 'sigo_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 => "r.guempelein\@sharee.bike"
);
}
#reservation end, this sigo request is still not defined
elsif($todo eq "reserve_end"){
$endpoint .= "reserve/end";
%json = (
reservationId => "$ctpos->{txt10}",
email => "r.guempelein\@sharee.bike"
);
}
#rental start
elsif($todo eq "rental"){
$endpoint .= "rental";
my $sig_serial = $ctpos->{txt22} || $ct_bike->{txt22};
%json = (
bikeId => "$sig_serial",
#reservationId => "$ctpos->{txt10}",#not in sig implemented
email => "r.guempelein\@sharee.bike"
);
}
#rental end
elsif($todo eq "rental_end"){
$endpoint .= "rental/end";
%json = (
rentalId => "$ctpos->{txt11}",
email => "r.guempelein\@sharee.bike"
);
#keep in mind, it will return no json, just text: "Rental Ended"
}
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_sigo($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 sigo_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 sigo_post $todo raw response_in with status_line: $ret_status\n" . Dumper($ret_json) . "\n";
#warn $@;
print FILE "warn:" . $@ . "\n";
}
}
if(ref($sig_book) ne "HASH"){
$sig_book = {
rentalId => "",
reservationId => ""
};
print FILE "<--- $now_dt failure sig_booking sigo_post $todo , reset sig_book hash to empty\n";
}
close(FILE);
return $sig_book;
}
#main GET
sub get_sigo {
my $self = shift;
my $endpoint = shift || "";
my $rest_json = shift || "";
my $sigo_request = "$endpoint";
print FILE "===> GET sigo >> " . $sigo_request . "\n" . $rest_json . "\n";
my $req = HTTP::Request->new(GET => "$sigo_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_sigo {
my $self = shift;
my $endpoint = shift || "";
my $rest_json = shift || "";
my $sigo_request = "$endpoint";
print FILE "===> POST sigo >> " . $sigo_request . "\n" . $rest_json . "\n";
my $req = HTTP::Request->new(POST => "$sigo_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;