diff --git a/.eslintrc.js b/.eslintrc.js index 64950ee0d6e..20d504355b4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,7 @@ module.exports = { }], "plugins": [ "html", - "php-markup", +// "php-markup", ], "rules": { "camelcase": "off", diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 6b561a1166a..878ec294a37 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -295,6 +295,7 @@ CREATE TABLE `Filters` ( `AutoMoveTo` smallint(5) unsigned NOT NULL default 0, `AutoCopy` tinyint(3) unsigned NOT NULL default '0', `AutoCopyTo` smallint(5) unsigned NOT NULL default 0, + `AutoPlateRecognize` tinyint(3) unsigned NOT NULL default '0', `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', `Concurrent` tinyint(1) unsigned NOT NULL default '0', @@ -315,6 +316,7 @@ CREATE TABLE `Frames` ( `TimeStamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, `Delta` decimal(8,2) NOT NULL default '0.00', `Score` smallint(5) unsigned NOT NULL default '0', + `Data_json` TEXT, PRIMARY KEY (`Id`), INDEX `EventId_idx` (`EventId`), KEY `Type` (`Type`), @@ -769,8 +771,9 @@ insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{" 1/*AutoDelete*/, 0/*AutoMove*/,0/*MoveTo*/, 0/*AutoCopy*/,0/*CopyTo*/, + 0/*AutoPlateRecognize*/, 0/*UpdateDiskSpace*/,1/*Background*/,0/*Concurrent*/); -insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',0,0,0,0,0,0,'',0,0,0,0,0,1,1,0); +insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',0,0,0,0,0,0,'',0,0,0,0,0,0,1,1,0); -- -- Add in some sample control protocol definitions diff --git a/db/zm_update-pr.sql b/db/zm_update-pr.sql new file mode 100644 index 00000000000..0fc085497d7 --- /dev/null +++ b/db/zm_update-pr.sql @@ -0,0 +1,27 @@ +-- +-- Add PlateRecognizer.com stuff +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoPlateRecognize' + ) > 0, +"SELECT 'Column AutoPlateRecognize already exists in Filters'", +"ALTER TABLE Filters ADD `AutoPlateRecognize` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoCopy`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Frames' + AND column_name = 'Data_json' + ) > 0, +"SELECT 'Column Data_json already exists in Frames'", +"ALTER TABLE `Frames` ADD `Data_json` text AFTER `Score`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index de4eb69848c..37a01934e45 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -4007,6 +4007,40 @@ our @options = ( type => $types{boolean}, category => 'logging', }, + { + name => 'ZM_PLATERECOGNIZER_ENABLE', + default => 'no', + type => $types{boolean}, + description => 'Whether to enable support for platerecognizer.com\'s APLR service.', + help => 'This will cause options to appear in filters and frame + views to submit images to platereconizer.com\'s service + which will return detected license plate information which + will be stored in ZM and presented when viewing events.', + category => 'plugins', + }, + { + name => 'ZM_PLATERECOGNIZER_API_TOKEN', + default => '', + type => $types{string}, + description => 'Your Api Key token. You must register at platerecognizer.com and cutnpaste your API Token here.', + category => 'plugins', + }, + { + name => 'ZM_PLATERECOGNIZER_URL', + default => 'https://api.platerecognizer.com/v1/plate-reader/', + type => $types{url}, + description => 'Endpoint URL to submit images to', + help => 'Do not change this unless you are using a local SDK install. Please consult docs.platerecognizer.com.', + category => 'plugins', + }, + { + name => 'ZM_PLATERECOGNIZER_REGION', + default => '', + type => $types{string}, + description => 'Specify local region to improve plate detection', + help => 'See http://docs.platerecognizer.com/#regions-supported for values.', + category => 'plugins', + } ); our %options_hash = map { ( $_->{name}, $_ ) } @options; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm b/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm index 6ce8f9ca06f..9f78102efe2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm @@ -33,7 +33,8 @@ require ZoneMinder::Object; use parent qw(ZoneMinder::Object); -use vars qw/ $table $primary_key %fields /; +use vars qw/ $table $primary_key %fields $debug /; +$debug = 1; $table = 'Frames'; $primary_key = 'Id'; @@ -51,6 +52,17 @@ sub Event { return new ZoneMinder::Event( $_[0]{EventId} ); } # end sub Event +sub Path { + my $self = shift; + my $show = @_ ? shift : 'capture'; + + return sprintf( + '%s/%0'.$ZoneMinder::Config{ZM_EVENT_IMAGE_DIGITS}.'d-%s.jpg', + $self->Event()->Path(), $$self{FrameId}, $show + ); +} + + 1; __END__ diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 1e0a16cdab3..d7dc5f40de0 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -113,8 +113,10 @@ void Zone::Setup( } if ( config.record_diag_images ) { - snprintf(diag_path, sizeof(diag_path), config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); - if (config.record_diag_images_fifo) + snprintf(diag_path, sizeof(diag_path), + config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", + monitor->getStorage()->Path(), id); + if ( config.record_diag_images_fifo ) FifoStream::fifo_create_if_missing(diag_path); pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); } else { @@ -129,7 +131,7 @@ Zone::~Zone() { delete[] ranges; } -void Zone::RecordStats( const Event *event ) { +void Zone::RecordStats(const Event *event) { static char sql[ZM_SQL_MED_BUFSIZ]; db_mutex.lock(); snprintf(sql, sizeof(sql), @@ -245,7 +247,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { if ( config.record_diag_images_fifo ) { FifoDebug(5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}", - id,alarm_pixels, pixel_diff); + id, alarm_pixels, pixel_diff); } if ( alarm_pixels ) { @@ -262,11 +264,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { return false; } - if ( max_alarm_pixels != 0 ) - score = (100*alarm_pixels)/max_alarm_pixels; - else - score = (100*alarm_pixels)/polygon.Area(); - + score = (100*alarm_pixels)/(max_alarm_pixels?max_alarm_pixels:polygon.Area()); if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug(5, "Current score is %d", score); @@ -356,8 +354,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { if ( check_method >= BLOBS ) { Debug(5, "Checking for blob pixels"); - typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; - BlobStats blob_stats[256]; + // ICON FIXME Would like to get rid of this memset memset(blob_stats, 0, sizeof(BlobStats)*256); uint8_t *spdiff; uint8_t last_x, last_y; @@ -367,22 +364,15 @@ bool Zone::CheckAlarms(const Image *delta_image) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; - pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); + pdiff = (uint8_t*)diff_image->Buffer(lo_x, y); for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { if ( *pdiff == WHITE ) { Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff); - //last_x = (x>lo_x)?*(pdiff-1):0; - //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; - last_x = 0; - if ( x > 0 ) { - if ( (x-1) >= lo_x ) { - last_x = *(pdiff-1); - } - } + last_x = ((x > 0) && ( (x-1) >= lo_x )) ? *(pdiff-1) : 0; last_y = 0; - if (y > 0 ) { + if ( y > 0 ) { if ( (y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x ) { last_y = *(pdiff-diff_width); } @@ -629,7 +619,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { } if ( max_blob_pixels != 0 ) - score = (100*alarm_blob_pixels)/(max_blob_pixels); + score = (100*alarm_blob_pixels)/max_blob_pixels; else score = (100*alarm_blob_pixels)/polygon.Area(); @@ -756,67 +746,42 @@ bool Zone::CheckAlarms(const Image *delta_image) { } bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) { - Debug(3, "Parsing polygon string '%s'", poly_string); - - char *str_ptr = new char[strlen(poly_string)+1]; - char *str = str_ptr; - strcpy(str, poly_string); + char *str = (char *)poly_string; char *ws; + char *cp; int n_coords = 0; int max_n_coords = strlen(str)/4; Coord *coords = new Coord[max_n_coords]; - while( true ) { - if ( *str == '\0' ) { - break; - } - ws = strchr(str, ' '); - if ( ws ) { - *ws = '\0'; - } - char *cp = strchr(str, ','); + while ( *str != '\0' ) { + cp = strchr(str, ','); if ( !cp ) { Error("Bogus coordinate %s found in polygon string", str); - delete[] coords; - delete[] str_ptr; - return false; - } else { - *cp = '\0'; - char *xp = str; - char *yp = cp+1; - - int x = atoi(xp); - int y = atoi(yp); - - Debug(3, "Got coordinate %d,%d from polygon string", x, y); -#if 0 - if ( x < 0 ) - x = 0; - else if ( x >= width ) - x = width-1; - if ( y < 0 ) - y = 0; - else if ( y >= height ) - y = height-1; -#endif - coords[n_coords++] = Coord( x, y ); - } + break; + } + int x = atoi(str); + int y = atoi(cp+1); + Debug(3, "Got coordinate %d,%d from polygon string", x, y); + coords[n_coords++] = Coord(x, y); + + ws = strchr(cp+2, ' '); if ( ws ) str = ws+1; else break; - } - polygon = Polygon(n_coords, coords); + } // end while ! end of string - Debug(3, "Successfully parsed polygon string"); - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); + if ( n_coords > 2 ) { + Debug(3, "Successfully parsed polygon string %s", str); + polygon = Polygon(n_coords, coords); + } else { + Error("Not enough coordinates to form a polygon!"); + n_coords = 0; + } delete[] coords; - delete[] str_ptr; - - return true; -} + return n_coords ? true : false; +} // end bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) { Debug(3, "Parsing zone string '%s'", zone_string); @@ -825,13 +790,12 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P char *str = str_ptr; strcpy(str, zone_string); - char *ws = strchr(str, ' '); - if ( !ws ) { - Debug(3, "No initial whitespace found in zone string '%s', finishing", str); - } zone_id = strtol(str, 0, 10); Debug(3, "Got zone %d from zone string", zone_id); + + char *ws = strchr(str, ' '); if ( !ws ) { + Debug(3, "No initial whitespace found in zone string '%s', finishing", str); delete[] str_ptr; return true; } @@ -839,13 +803,11 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P *ws = '\0'; str = ws+1; - ws = strchr(str, ' '); - if ( !ws ) { - Debug(3, "No secondary whitespace found in zone string '%s', finishing", zone_string); - } colour = strtol(str, 0, 16); Debug(3, "Got colour %06x from zone string", colour); + ws = strchr(str, ' '); if ( !ws ) { + Debug(3, "No secondary whitespace found in zone string '%s', finishing", zone_string); delete[] str_ptr; return true; } @@ -853,27 +815,28 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P str = ws+1; bool result = ParsePolygonString(str, polygon); - - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); - delete[] str_ptr; return result; -} +} // end bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) int Zone::Load(Monitor *monitor, Zone **&zones) { static char sql[ZM_SQL_MED_BUFSIZ]; db_mutex.lock(); - snprintf(sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id()); + snprintf(sql, sizeof(sql), "SELECT Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0," + "MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels," + "FilterX,FilterY,MinFilterPixels,MaxFilterPixels," + "MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs," + "OverloadFrames,ExtendAlarmFrames" + " FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id()); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); db_mutex.unlock(); return 0; } - MYSQL_RES *result = mysql_store_result( &dbconn ); + MYSQL_RES *result = mysql_store_result(&dbconn); if ( !result ) { Error("Can't use query result: %s", mysql_error(&dbconn)); db_mutex.unlock(); @@ -942,7 +905,13 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { } else if ( atoi(dbrow[2]) == Zone::PRIVACY ) { zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon); } - zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames); + zones[i] = new Zone( + monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, + (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, + MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), + MinFilterPixels, MaxFilterPixels, + MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, + OverloadFrames, ExtendAlarmFrames); } // end foreach row mysql_free_result(result); return n_zones; @@ -951,9 +920,9 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { bool Zone::DumpSettings(char *output, bool /*verbose*/) { output[0] = 0; - sprintf( output+strlen(output), " Id : %d\n", id ); - sprintf( output+strlen(output), " Label : %s\n", label ); - sprintf( output+strlen(output), " Type: %d - %s\n", type, + sprintf(output+strlen(output), " Id : %d\n", id ); + sprintf(output+strlen(output), " Label : %s\n", label ); + sprintf(output+strlen(output), " Type: %d - %s\n", type, type==ACTIVE?"Active":( type==INCLUSIVE?"Inclusive":( type==EXCLUSIVE?"Exclusive":( @@ -1014,10 +983,10 @@ void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsig *pdiff = BLACK; } } - } + } // end for y = lo_y to hi_y /* Store the results */ *pixel_count = pixelsalarmed; *pixel_sum = pixelsdifference; Debug(7, "STORED pixelsalarmed(%d), pixelsdifference(%d)", pixelsalarmed, pixelsdifference); -} +} // end void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum) diff --git a/src/zm_zone.h b/src/zm_zone.h index 0c7b26a579e..7d77977259b 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -32,15 +32,14 @@ class Monitor; // This describes a 'zone', or an area of an image that has certain // detection characteristics. // -class Zone -{ +class Zone { protected: - struct Range - { + struct Range { int lo_x; int hi_x; int off_x; }; + typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; public: typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, PRECLUSIVE, INACTIVE, PRIVACY } ZoneType; @@ -52,8 +51,8 @@ class Zone int id; char *label; - ZoneType type; - Polygon polygon; + ZoneType type; + Polygon polygon; Rgb alarm_rgb; CheckMethod check_method; @@ -67,17 +66,18 @@ class Zone int min_filter_pixels; int max_filter_pixels; + BlobStats blob_stats[256]; int min_blob_pixels; int max_blob_pixels; int min_blobs; int max_blobs; - int overload_frames; + int overload_frames; int extend_alarm_frames; // Outputs/Statistics - bool alarmed; - bool was_alarmed; + bool alarmed; + bool was_alarmed; int pixel_diff; unsigned int alarm_pixels; int alarm_filter_pixels; @@ -139,8 +139,7 @@ class Zone inline Coord GetAlarmCentre() const { return( alarm_centre ); } inline unsigned int Score() const { return( score ); } - inline void ResetStats() - { + inline void ResetStats() { alarmed = false; was_alarmed = false; pixel_diff = 0; diff --git a/web/includes/Frame.php b/web/includes/Frame.php index 1117459dc2f..883bd2b0be5 100644 --- a/web/includes/Frame.php +++ b/web/includes/Frame.php @@ -14,6 +14,7 @@ class Frame extends ZM_Object { 'TimeStamp' => 0, 'Delta' => 0.00, 'Score' => 0, + 'Data_json' => '', ); public static function find( $parameters = array(), $options = array() ) { @@ -29,7 +30,7 @@ public function Storage() { } public function Event() { - return new Event( $this->{'EventId'} ); + return Event::find_one(array('Id'=>$this->{'EventId'})); } public function getImageSrc( $show='capture' ) { @@ -37,5 +38,39 @@ public function getImageSrc( $show='capture' ) { #return '?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc + public function Path($show='capture') { + return sprintf( + '%s/%0'.ZM_EVENT_IMAGE_DIGITS.'d-%s.jpg', + $this->Event()->Path(), $this->FrameId(), $show + ); + } + + public function Data_json() { + if ( func_num_args( ) ) { + $this->{'Data_json'} = func_get_arg(0);; + $this->{'Data'} = jsonDecode($this->{'Data_json'}); + } + return $this->{'Data_json'}; + } + + public function Data() { + if ( func_num_args( ) ) { + $this->{'Data'} = func_get_arg(0);; + $this->{'Data_json'} = jsonEncode($this->{'Data'}); + } + if ( !array_key_exists('Data', $this) ) { + if ( array_key_exists('Data_json', $this) and $this->{'Data_json'} ) { + $this->{'Data'} = jsonDecode($this->{'Data_json'}); + } else { + $this->{'Data'} = array(); + } + } else { + if ( !is_array($this->{'Data'}) ) { + # Handle existence of both Data_json and Data in the row + $this->{'Data'} = jsonDecode($this->{'Data_json'}); + } + } + return $this->{'Data'}; + } } # end class Frame ?> diff --git a/web/includes/Frame_Data.php b/web/includes/Frame_Data.php new file mode 100644 index 00000000000..e9490ab1e50 --- /dev/null +++ b/web/includes/Frame_Data.php @@ -0,0 +1,24 @@ +{'data'} = $data; + } # end function __construct + + /* Will output a useful representation of the data. */ + public function to_string() { + $data = $this->{'data'}; + + if ( isset($data['type']) ) { + if ( $data['type'] == 'PLATE_RECOGNIZER' ) { + return 'plate: '.$data['data']['results'][0]['plate'].' score: '.$data['data']['results'][0]['score']; + } else { + return print_r($data,true); + } + } else { + return print_r($data,true); + } + } // end public to_string +} +?> diff --git a/web/includes/Object.php b/web/includes/Object.php index cc595cd7477..72153fa8a86 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -292,7 +292,6 @@ public function save($new_values = null) { $table = $class::$table; if ( $new_values ) { - //Logger::Debug("New values" . print_r($new_values, true)); $this->set($new_values); } diff --git a/web/includes/actions/frame.php b/web/includes/actions/frame.php new file mode 100644 index 00000000000..46a515d095d --- /dev/null +++ b/web/includes/actions/frame.php @@ -0,0 +1,95 @@ +$_REQUEST['eid'],'FrameId'=>$fid) ); +if ( !$Frame ) { + ZM\Error("Frame not found for event $eid frame $fid"); + return; +} +if ( $action == 'delete' ) { + $Frame->delete(); + $view = 'none'; + $refreshParent = true; +} else if ( $action == 'do_alpr' ) { + $path = $Frame->Path();#defaults to capture image + ZM\Logger::Debug("Using frame path $path"); + $i = file_get_contents($path); + if ( !$i ) { + ZM\Error("No image from $path"); + return; + } + + $postdata = array( + 'upload' => $i, + #'regions' => ZM_PLATERECOGNIZER_REGION, + ); + + $eol = "\r\n"; + $data = ''; + $boundary = '---------------------' . substr(md5(rand(0, 32000)), 0, 10); + if ( is_array($postdata) ) { + foreach ( $postdata as $key => $val ) { + $data .= '--' . $boundary . $eol; + $data .= 'Content-Disposition: form-data; name='.$key.'; filename=frame.jpg;'.$eol.$eol.$val.$eol; + } + } + $data .= '--'.$boundary.$eol; + + $response = do_post_request( + ZM_PLATERECOGNIZER_URL, + $data, + implode($eol, array( + #'Accept: */*', + 'Authorization: Token '. ZM_PLATERECOGNIZER_API_TOKEN, + #'Connection: close', + 'Content-type: multipart/form-data; boundary='.$boundary + ) + ) + #'Content-type: application/x-www-form-urlencoded; charset=UTF-8')) + ); + ZM\Logger::Debug('Response: ' . print_r($response,true)); + $previous_data = $Frame->Data(); + ZM\Logger::Debug('Old data: ' . print_r($previous_data,true)); + $previous_data[] = array('type'=>'PLATE_RECOGNIZER','time'=>time(),'data'=>json_decode($response)); + // Frame->json_data is an array of responses, so append this one + $Frame->save( array('Data'=>$previous_data) ); +} +?> diff --git a/web/skins/classic/views/frame.php b/web/skins/classic/views/frame.php index 64f1035b389..e1a197d064c 100644 --- a/web/skins/classic/views/frame.php +++ b/web/skins/classic/views/frame.php @@ -24,6 +24,7 @@ } require_once('includes/Frame.php'); +require_once('includes/Frame_Data.php'); $eid = validInt($_REQUEST['eid']); if ( !empty($_REQUEST['fid']) ) @@ -48,8 +49,6 @@ $nextFid = $fid+1; $lastFid = $maxFid; -$alarmFrame = $Frame->Type() == 'Alarm'; - if ( isset( $_REQUEST['scale'] ) ) { $scale = validNum($_REQUEST['scale']); } else if ( isset( $_COOKIE['zmWatchScale'.$Monitor->Id()] ) ) { @@ -88,9 +87,12 @@ diff --git a/web/skins/classic/views/js/frame.js b/web/skins/classic/views/js/frame.js index a0a30eed43e..31a8ea75ccd 100644 --- a/web/skins/classic/views/js/frame.js +++ b/web/skins/classic/views/js/frame.js @@ -24,7 +24,7 @@ function changeScale() { img.css('width', newWidth + 'px'); img.css('height', newHeight + 'px'); } - Cookie.write( 'zmWatchScale', scale, {duration: 10*365} ); + Cookie.write('zmWatchScale', scale, {duration: 10*365}); $j.each(controlsLinks, function(k, anchor) { //Make frames respect scale choices if ( anchor ) { anchor.prop('href', anchor.prop('href').replace(/scale=.*&/, 'scale=' + scale + '&')); @@ -36,6 +36,11 @@ if ( scale == 'auto' ) { $j(document).ready(changeScale); } +function deleteClicked() { + window.location.href = '?view=frame&action=delete&eid='+eid+'&fid='+fid; +} + document.addEventListener('DOMContentLoaded', function onDCL() { document.getElementById('scale').addEventListener('change', changeScale); + document.getElementById('delete_button').addEventListener('click', deleteClicked); }); diff --git a/web/skins/classic/views/js/frame.js.php b/web/skins/classic/views/js/frame.js.php index 572587f8288..2af372ee655 100644 --- a/web/skins/classic/views/js/frame.js.php +++ b/web/skins/classic/views/js/frame.js.php @@ -1,3 +1,5 @@ var scale = ''; +var eid = ''; +var fid = ''; var SCALE_BASE = ; diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index e1a77b8748c..caeddde129b 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -37,6 +37,7 @@ $tabs['logging'] = translate('Logging'); $tabs['network'] = translate('Network'); $tabs['mail'] = translate('Email'); +$tabs['plugins'] = translate('Plugins'); $tabs['upload'] = translate('Upload'); $tabs['x10'] = translate('X10'); $tabs['highband'] = translate('HighBW'); @@ -291,13 +292,10 @@ APIs are disabled. To enable, please turn on OPT_USE_API in Options->System"; - } - else { + } else { ?>
@@ -351,7 +349,6 @@ function updateSelected() { @@ -360,14 +357,12 @@ function updateSelected() { /> - +
- - FrameId() - $previousBulkFrame['FrameId']) / ($nextBulkFrame['FrameId'] - $previousBulkFrame['FrameId']); $Frame->Delta($previousBulkFrame['Delta'] + floor( 100* ( $nextBulkFrame['Delta'] - $previousBulkFrame['Delta'] ) * $percentage )/100); - ZM\Logger::Debug("Got virtual frame from Bulk Frames previous delta: " . $previousBulkFrame['Delta'] . " + nextdelta:" . $nextBulkFrame['Delta'] . ' - ' . $previousBulkFrame['Delta'] . ' * ' . $percentage ); + ZM\Logger::Debug('Got virtual frame from Bulk Frames previous delta: ' . $previousBulkFrame['Delta'] . ' + nextdelta:' . $nextBulkFrame['Delta'] . ' - ' . $previousBulkFrame['Delta'] . ' * ' . $percentage ); } else { ZM\Fatal('No Frame found for event('.$_REQUEST['eid'].') and frame id('.$_REQUEST['fid'].')'); }