Add License Plate Recognizer integration and vehicle product suggestion route

- Implemented `licensePlateRecognizer` module with configuration classes (`enabled`, `api_key`) and API handling.
- Introduced `moduleConfigRoute` endpoints for fetching and updating `licensePlateRecognizer` configurations.
- Added `vehicleProductSuggestionRoute` with logic to fetch product suggestions based on vehicle plates.
- Extended `moduleScannerRoute` with license plate recognition test and exception handling.
- Updated `index.php` to include `licensePlateRecognizer` class.
This commit is contained in:
Jeppe Bundgaard
2025-09-01 10:11:37 +02:00
parent 7a852f5b54
commit 81aa406fb6
9 changed files with 378 additions and 5 deletions
@@ -0,0 +1,89 @@
<?php
namespace classes;
require_once WD . '/modules/licenseplaterecognizer/licenseplaterecognizer_c.php';
use Exception;
use interfaces\licenseplaterecognizer_i;
use licenseplaterecognizer\licenseplaterecognizer_c;
class licenseplaterecognizer implements licenseplaterecognizer_i
{
/**
* The configuration of the module
* @var licenseplaterecognizer_c
*/
public licenseplaterecognizer_c $config;
/**
* API URL
* @var string
*/
private string $api_url = 'https://api.platerecognizer.com/v1/plate-reader/';
public function __construct()
{
$this->config = new licenseplaterecognizer_c();
}
/**
* @inheritDoc
*/
public function requireModuleEnabled(): void
{
if (!(bool)$this->config->enabled->getVariableValue()) {
throw new Exception('licenseplaterecognizer module is not enabled.');
}
}
/**
* @inheritDoc
* @throws Exception If the module is not enabled or if there is an error in the API request
* @throws Exception If the API response cannot be parsed
*/
public function licenseplaterecognizer(string $base64_image): array
{
$image_processor = new image_processor();
//ADD PARAMETER IN REQUEST LIKE regions
$data = array(
'upload' => $base64_image,
'regions' => 'us-ca' // Optional
);
// Prepare new cURL resource
$ch = curl_init('https://api.platerecognizer.com/v1/plate-reader/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
// Set HTTP Header for POST request
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: Token " . $this->config->api_key->getVariableValue()
)
);
// Submit the POST request and close cURL session handle
$result = curl_exec($ch);
curl_close($ch);
$response_data = json_decode($result, true);
if (isset($response_data['results']) && count($response_data['results']) > 0) {
return [
'success' => true,
'license_plate_number' => $response_data['results'][0]['plate'] ?? null,
'confidence' => $response_data['results'][0]['score'] ?? null,
'raw_response' => $response_data,
];
} else {
return [
'success' => false,
'message' => 'No license plate detected.',
'raw_response' => $response_data,
];
}
}
}
+1
View File
@@ -70,6 +70,7 @@ require_once 'classes/entra.php';
require_once 'classes/limble.php';
require_once 'classes/ocr_space.php';
require_once 'classes/openai.php';
require_once 'classes/licenseplaterecognizer.php';
require_once 'classes/image_processor.php';
require_once 'classes/upload_store.php';
@@ -0,0 +1,12 @@
<?php
namespace interfaces;
require_once WD . '/interfaces/universal_module_i.php';
interface licenseplaterecognizer_i extends universal_module_i
{
/**
* licenseplaterecognizer - Get the plate number from the image.
* @param string $base64_image The base64 encoded image to be processed.
* @return array An array containing the plate number and other relevant information.
*/
public function licenseplaterecognizer(string $base64_image): array;
}
@@ -0,0 +1,29 @@
<?php
namespace licenseplaterecognizer\config;
use Exception;
use traits\module_config_variable;
class licenseplaterecognizer_api_key_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'licenseplaterecognizer',
'api_key',
'string',
false,
null,
'The secret key for licenseplaterecognizer API',
'1',
true,
''
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace licenseplaterecognizer\config;
use Exception;
use traits\module_config_variable;
class licenseplaterecognizer_enabled_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'licenseplaterecognizer',
'enabled',
'bool',
true,
null,
'Whether licenseplaterecognizer is enabled',
'1',
false,
'false'
);
}
}
@@ -0,0 +1,36 @@
<?php
namespace licenseplaterecognizer;
require_once WD . '/modules/licenseplaterecognizer/config/licenseplaterecognizer_enabled_c.php';
require_once WD . '/modules/licenseplaterecognizer/config/licenseplaterecognizer_api_key_c.php';
use licenseplaterecognizer\config\licenseplaterecognizer_enabled_c;
use licenseplaterecognizer\config\licenseplaterecognizer_api_key_c;
use traits\module_config_t;
class licenseplaterecognizer_c
{
use module_config_t;
/**
* The status of licenseplaterecognizer, whether it is enabled or not
* @var licenseplaterecognizer_enabled_c
*/
public licenseplaterecognizer_enabled_c $enabled;
/**
* The API key for licenseplaterecognizer
* @var licenseplaterecognizer_api_key_c
*/
public licenseplaterecognizer_api_key_c $api_key;
public function __construct()
{
$this->setupConfig('licenseplaterecognizer');
$this->allowUpdate([
licenseplaterecognizer_enabled_c::class,
licenseplaterecognizer_api_key_c::class,
]);
$this->enabled = new licenseplaterecognizer_enabled_c();
$this->api_key = new licenseplaterecognizer_api_key_c();
}
}
@@ -563,5 +563,43 @@ class moduleConfigRoute
'modules_openai_config' => 'Update openai config'
]
);
/** LicensePlateRecognizer config > GET */
$this->get('/licenseplaterecognizer/config', function () {
global $response;
$this->requirePermission('modules_licenseplaterecognizer_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('licenseplaterecognizer_config', 'global', 1, $user->id, 'LICENSEPLATERECOGNIZER_CONFIG', 'Successfully fetched licenseplaterecognizer config');
$response->success(
(new \classes\licenseplaterecognizer())->config->getConfigRequest()
);
} else {
(new logs_o())->add('licenseplaterecognizer_config', 'global', 1, 0, 'LICENSEPLATERECOGNIZER_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
},
[
'modules_licenseplaterecognizer_config' => 'Get licenseplaterecognizer config'
]
);
/** LicensePlateRecognizer config > POST */
$this->post('/licenseplaterecognizer/config', function () {
global $response;
$this->requirePermission('modules_licenseplaterecognizer_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('licenseplaterecognizer_config', 'global', 1, $user->id, 'LICENSEPLATERECOGNIZER_CONFIG', 'Successfully updated licenseplaterecognizer config');
$response->success(
(new \classes\licenseplaterecognizer())->config->postConfigRequest()
);
} else {
(new logs_o())->add('licenseplaterecognizer_config', 'global', 1, 0, 'LICENSEPLATERECOGNIZER_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
},
[
'modules_licenseplaterecognizer_config' => 'Update licenseplaterecognizer config'
]
);
}
}
@@ -4,10 +4,12 @@ namespace routes;
use classes\authentication;
use classes\image_processor;
use classes\licenseplaterecognizer;
use classes\openai;
use classes\response;
use classes\router;
use classes\upload_store;
use Exception;
use objects\logs_o;
use traits\route_t;
@@ -23,8 +25,38 @@ class moduleScannerRoute
$this->get('/modules/scanner/test', function () {
global $response;
$base64_image = '';
//self::requirePermission('modules_scanner_test');
$image_processor = new image_processor();
// CREATE FILE READY TO UPLOAD WITH CURL
$file = realpath('example.jpg');
if (function_exists('curl_file_create')) { // php 5.5+
$cFile = curl_file_create($file);
} else {
$cFile = '@' . realpath($file);
}
//ADD PARAMETER IN REQUEST LIKE regions
$data = array(
'upload' => $cFile,
'regions' => 'us-ca' // Optional
);
// Prepare new cURL resource
$ch = curl_init('https://api.platerecognizer.com/v1/plate-reader/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
// Set HTTP Header for POST request
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: Token d7fff863b815cde9fe3f83d687290399ef4854f4", // TODO: Move to settings or environment variable.
)
);
// Submit the POST request and close cURL session handle
$result = curl_exec($ch);
print_r($result);exit;
curl_close($ch);
$response->success('Scanner module test executed successfully.');
},
[
@@ -39,14 +71,41 @@ class moduleScannerRoute
//self::requirePermission('modules_scanner_lpr');
if (empty($base64_image)) {
$response->error('Base64 image is required.');}
$image_processor = new image_processor();
$image_processor->set_image_base64($base64_image);
$uploads = new upload_store();
$object_name = $uploads->storeTempImageFromBase64(
$base64_image
);
//echo $object_name;
//echo "License Plate Recognition result:\n";
// Uncomment the line below to use the actual license plate recognizer.
$lpr_result = (new licenseplaterecognizer())->licenseplaterecognizer($base64_image);
if ($lpr_result['success']) {
// Set the LICENSE_PLATE_NUMBER to uppercase and remove spaces.
$lpr_result['license_plate_number'] = strtoupper(str_replace(' ', '', $lpr_result['license_plate_number']));
// If the confidence is below 90%, consider it a failure.
if (isset($lpr_result['confidence']) && $lpr_result['confidence'] < 0.9) {
throw new Exception('License plate recognition confidence too low. Score: ' . $lpr_result['confidence'] . ' Plate: ' . $lpr_result['license_plate_number'] . ' Raw: ' . json_encode($lpr_result['raw_response']));
}
// Success
$response->success(['success' => true, 'license_plate_number' => $lpr_result['license_plate_number']]);
} else {
throw new Exception('License plate extraction failed.');
//$response->error($lpr_result['message'] ?? 'License plate recognition failed.', $lpr_result);
}
exit;
// For debugging purposes, we will simulate recognized license plates.
$openai = new openai();
//$response->success('CU61297');
$registration_numbers_debug = [
'EC21233',
'EC21234',
'EC21235',
];
$random_index = rand(0, count($registration_numbers_debug) - 1);
$object_name = $registration_numbers_debug[$random_index];
// Attempt to recognize the license plate number from the image.
//Debug: TODO: Remove this.
$response->success(['success' => true, 'license_plate_number' => $object_name]);
$response->success($openai->lpr($object_name));
},
[
@@ -0,0 +1,80 @@
<?php
namespace routes;
use classes\authentication;
use classes\motorapi;
use Exception;
use objects\logs_o;
use objects\motorapi_lookups_o;
use objects\orders_o;
use traits\route_t;
class vehicleProductSuggestionRoute
{
use route_t;
public function run(): void
{
$this->get(
'/vehicle/product-suggestions',
function () {
// Require the user to be logged in
global $response;
$this->requirePermission('vehicle_product_suggestions');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Make sure the vehicle plate(s) is provided
$reg_1 = null; $reg_2 = null; $reg_3 = null;
foreach (['reg_1', 'reg_2', 'reg_3'] as $key) {
if ($this->isParametersSet([$key])) {
${$key} = (string)$this->fromRequest($key);
self::requireType(${$key}, self::type_string());
self::requireMinLength($key, 1);
self::requireMaxLength($key, 10);
}
}
if (!$reg_1 && !$reg_2 && !$reg_3) {
$response->error('At least one vehicle plate must be provided', 400);
}
// Check if the vehicle has been previously added to an order.
// Check if the vehicle has been previously looked up.
} else {
// Log the incident
(new logs_o())->add('products', 'global', 1, 0, 'FETCH_VEHICLE_PRODUCT_SUGGESTIONS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'vehicle_product_suggestions' => 'Access to vehicle product suggestions based on internal and external data',
]
);
}
/**
*
* @throws Exception If the MotorAPI request fails, if the registration is invalid, or if the response is malformed
*/
protected function getVehicleMotorAPIValue(string $reg): ?object
{
// Get the MotorAPI response for the vehicle reg
$MotorAPI = new motorapi();
return $MotorAPI->getLicensePlateInformation($reg, false);
}
protected function getSimilarVehicles(array $values): array
{
$tmp_result = (new motorapi_lookups_o())->getCachedResultsBySubstring($values);
return array_map(
function ($item) {
return $item->result->value();
},
$tmp_result
);
}
}