source: branches/reyalp-ptp-live/core/motion_detector.c @ 1711

Revision 1711, 19.9 KB checked in by reyalp, 15 months ago (diff)

update from trunk 1672-1705

  • Property svn:eol-style set to native
Line 
1/*
2
3Motion detection module
4
5Author: mx3 (Max Sagaydachny) . win1251 ( Ìàêñèì Ñàãàéäà÷íûé )
6Email: win.drivers@gmail.com
7Skype: max_dtc
8ICQ#: 125-985-663
9Country: Ukraine
10Sity: Kharkiv
11
1220070912 mx3: first version
13
1420070918 mx3: speed optimization,
15
1620101201 CHDKLover: speed optimization
17
18*/
19
20#ifdef OPT_MD_DEBUG
21#define MD_REC_CALLS_CNT 2048
22#define MD_INFO_BUF_SIZE 4096
23#endif
24
25#include "motion_detector.h"
26#include "action_stack.h"
27#include "console.h"
28#include "keyboard.h"
29
30#include "gui.h"
31#include "gui_draw.h"
32
33
34#define MD_XY2IDX(x,y) ((y)*motion_detector->columns+x)
35
36enum {
37
38    MD_MEASURE_MODE_U=0,
39    MD_MEASURE_MODE_Y=1,
40    MD_MEASURE_MODE_V=2,
41    MD_MEASURE_MODE_R=3,
42    MD_MEASURE_MODE_G=4,
43    MD_MEASURE_MODE_B=5
44};
45
46enum {
47    MD_DO_IMMEDIATE_SHOOT=1,
48    MD_MAKE_DEBUG_LOG_FILE=2,
49    MD_MAKE_RAM_DUMP_FILE=4,
50    MD_NO_SHUTTER_RELEASE_ON_SHOOT=8
51};
52
53enum {
54    MD_REGION_NONE=0,
55    MD_REGION_INCLUDE=1,
56    MD_REGION_EXCLUDE=2
57};
58
59
60
61
62
63struct motion_detector_s {
64    int *curr; // points to buff1 or buff2
65    int *prev; // points to buff2 or buff1
66    int buff1[MOTION_DETECTOR_CELLS];
67    int buff2[MOTION_DETECTOR_CELLS];
68    int points[MOTION_DETECTOR_CELLS];
69
70    int columns;
71    int rows;
72    int threshold;
73    int pixel_measure_mode;
74    int timeout;
75    int measure_interval;
76
77    int last_measure_time;
78    int start_time;
79
80    int running;
81    int detected_cells;
82
83    int draw_grid;
84    int clipping_region_mode;
85    int clipping_region_row1;
86    int clipping_region_column1;
87    int clipping_region_row2;
88    int clipping_region_column2;
89
90    int previous_picture_is_ready;
91
92    int return_value;
93    int parameters;
94    int pixels_step;
95    int msecs_before_trigger;
96
97// debug
98#ifdef OPT_MD_DEBUG
99    int comp_calls_cnt;
100    int comp_calls[MD_REC_CALLS_CNT];
101#endif
102};
103
104static struct motion_detector_s *motion_detector=NULL;
105
106static void md_kbd_sched_immediate_shoot(int no_release)
107{
108    action_pop();// REMOVE MD ITEM
109
110    // stack operations are reversed!
111    if (!no_release)  // only release shutter if allowed
112    {
113      action_push_release(KEY_SHOOT_FULL);
114      action_push_delay(20);
115    }
116    action_push(AS_MOTION_DETECTOR); // it will removed right after exit from this function
117    kbd_key_press(KEY_SHOOT_FULL); // not a stack operation... pressing right now
118}
119
120static int clip(int v) {
121    if (v<0) v=0;
122    if (v>255) v=255;
123    return v;
124}
125
126
127// TODO add script interface, currently done when script ends
128void md_close_motion_detector()
129{
130    if (motion_detector)
131    {
132            free(motion_detector);
133        motion_detector=NULL;
134    }
135}
136
137
138int md_init_motion_detector(
139 int columns,
140 int rows,
141 int pixel_measure_mode,
142 int detection_timeout,
143 int measure_interval,
144 int threshold,
145 int draw_grid,
146 int clipping_region_mode,
147 int clipping_region_column1,
148 int clipping_region_row1,
149 int clipping_region_column2,
150 int clipping_region_row2,
151 int parameters,
152 int pixels_step,
153 int msecs_before_trigger
154){
155
156    if(!motion_detector) {
157        motion_detector=malloc(sizeof(struct motion_detector_s));
158        if(!motion_detector)
159            return 0; // TODO make sure callers handle this
160    }
161#ifdef OPT_MD_DEBUG
162    motion_detector->comp_calls_cnt=0;
163#endif
164    motion_detector->previous_picture_is_ready=0;
165    motion_detector->curr=motion_detector->buff1;
166    motion_detector->prev=motion_detector->buff2;
167
168    if(     pixel_measure_mode != MD_MEASURE_MODE_Y
169        &&  pixel_measure_mode != MD_MEASURE_MODE_U
170        &&  pixel_measure_mode != MD_MEASURE_MODE_V
171        &&  pixel_measure_mode != MD_MEASURE_MODE_R
172        &&  pixel_measure_mode != MD_MEASURE_MODE_G
173        &&  pixel_measure_mode != MD_MEASURE_MODE_B
174        ){
175        pixel_measure_mode = MD_MEASURE_MODE_Y;
176    }
177
178
179    if( columns<1 || rows<1 || columns * rows > MOTION_DETECTOR_CELLS ){
180        columns=3;
181        rows=3;
182    }
183
184    if(msecs_before_trigger<0){
185        msecs_before_trigger=0;
186    }
187
188    if (pixels_step<1){
189        pixels_step=1;
190    }
191
192    if(detection_timeout<0){
193        detection_timeout=0;
194    }
195
196    if(measure_interval<0) {
197        measure_interval=0;
198    }
199
200    if(threshold<0) {
201        threshold=0;
202    }
203
204
205    motion_detector->msecs_before_trigger=msecs_before_trigger;
206    motion_detector->parameters = parameters;
207    motion_detector->pixels_step=pixels_step;
208    motion_detector->columns=columns;
209    motion_detector->rows=rows;
210    motion_detector->return_value=0;
211
212
213    motion_detector->pixel_measure_mode=pixel_measure_mode;
214    motion_detector->timeout=detection_timeout;
215    motion_detector->measure_interval=measure_interval;
216    motion_detector->threshold=threshold;
217    motion_detector->draw_grid=draw_grid;
218
219
220    if (clipping_region_column1>clipping_region_column2){
221        motion_detector->clipping_region_column2=clipping_region_column1;
222        motion_detector->clipping_region_column1=clipping_region_column2;
223    } else {
224        motion_detector->clipping_region_column2=clipping_region_column2;
225        motion_detector->clipping_region_column1=clipping_region_column1;
226    }
227
228    if (clipping_region_row1>clipping_region_row2){
229        motion_detector->clipping_region_row2=clipping_region_row1;
230        motion_detector->clipping_region_row1=clipping_region_row2;
231    } else {
232        motion_detector->clipping_region_row2=clipping_region_row2;
233        motion_detector->clipping_region_row1=clipping_region_row1;
234    }
235
236    if (clipping_region_mode!=MD_REGION_NONE && clipping_region_mode!=MD_REGION_INCLUDE && clipping_region_mode!=MD_REGION_EXCLUDE){
237        clipping_region_mode=MD_REGION_NONE;
238    }
239    motion_detector->clipping_region_mode=clipping_region_mode;
240
241    motion_detector->detected_cells=0;
242    motion_detector->previous_picture_is_ready=0;
243    motion_detector->start_time=get_tick_count();
244
245    motion_detector->last_measure_time = motion_detector->start_time - motion_detector->measure_interval;
246
247    motion_detector->running=1;
248
249    action_push(AS_MOTION_DETECTOR);
250    draw_clear();
251
252    return 1;
253}
254
255#ifdef OPT_MD_DEBUG
256static void md_save_calls_history(){
257    char buf[200], fn[30];
258    char big[MD_INFO_BUF_SIZE];
259    int big_ln;
260    int calls,i, ln, fd;
261    static struct utimbuf t;
262    static struct tm *ttm;
263
264
265    if( (motion_detector->parameters & MD_MAKE_DEBUG_LOG_FILE) == 0 ){
266        return;
267    }
268
269
270    strcpy(fn,"A/MD_INFO.TXT");//,BUILD_NUMBER,motion_detector->pixels_step);
271    fd = open(fn, O_WRONLY|O_CREAT, 0777);
272    if( fd>=0) {
273        console_add_line("Writing info file...");
274        lseek(fd,0,SEEK_END);
275            ttm = get_localtime();
276        big_ln=sprintf(big,
277                                "\r\n--- %04u-%02u-%02u  %02u:%02u:%02u\r\n"
278                                "CHDK Ver: %s [ #%s ]\r\nBuild Date: %s %s\r\nCamera:  %s [ %s ]\r\n"
279                                "[%dx%d], threshold: %d, interval: %d, pixels step: %d\r\n"
280                                "region: [%d,%d-%d,%d], region type: %d\r\n"
281                                "wait interval: %d, parameters: %d, calls: %d, detected cells: %d\r\n",
282                                1900+ttm->tm_year, ttm->tm_mon+1, ttm->tm_mday, ttm->tm_hour, ttm->tm_min, ttm->tm_sec,
283                                HDK_VERSION, BUILD_NUMBER, __DATE__, __TIME__, PLATFORM, PLATFORMSUB,
284                                motion_detector->columns, motion_detector->rows, motion_detector->threshold, motion_detector->measure_interval, motion_detector->pixels_step,
285                                motion_detector->clipping_region_column1, motion_detector->clipping_region_row1, motion_detector->clipping_region_column2, motion_detector->clipping_region_row2, motion_detector->clipping_region_mode,
286                                motion_detector->msecs_before_trigger, motion_detector->parameters, motion_detector->comp_calls_cnt,
287                                motion_detector->detected_cells
288                );
289
290        calls = ( motion_detector->comp_calls_cnt < MD_REC_CALLS_CNT) ?motion_detector->comp_calls_cnt: MD_REC_CALLS_CNT;
291
292        for(i=0;i<calls;i++){
293            ln=sprintf(buf,"[%d] - %d\r\n",i,motion_detector->comp_calls[i]);
294            if(big_ln+ln>MD_INFO_BUF_SIZE){
295          write(fd,big,big_ln);
296                big_ln=0;
297            }
298            memcpy(big+big_ln,buf,ln+1);
299            big_ln+=ln;
300        }
301    write(fd,big,big_ln);
302        close(fd);
303      t.actime = t.modtime = time(NULL);
304    utime(fn, &t);
305    }
306}
307
308static void mx_dump_memory(void *img){
309    char fn[36];
310    int fd, i;
311    static int cnt=0;
312
313    started();
314    mkdir("A/MD");
315
316        do {
317            cnt++;
318            sprintf(fn, "A/MD/%04d.FB", cnt);
319            fd = open(fn, O_RDONLY, 0777);
320
321            if(fd>=0){
322                close(fd);
323            }
324        } while(fd>=0);
325
326
327                sprintf(fn, "A/MD/%04d.FB", cnt );
328                fd = open(fn, O_WRONLY|O_CREAT, 0777);
329                if (fd) {
330            write(fd, img, camera_screen.width*vid_get_viewport_height()*3);
331            close(fd);
332                }
333  vid_bitmap_refresh();
334  finished();
335
336}
337#else
338#define md_save_calls_history()
339#define mx_dump_memory(x)
340#endif
341
342
343static int md_running(){
344        return motion_detector?motion_detector->running:0;
345}
346
347
348int md_detect_motion(void){
349    int *tmp;
350    unsigned char * img;
351    int vp_w, vp_h, idx, tmp2, tick, in_clipping_region, x_step, y_step, x_end, y_end;
352    int val;
353    int cy,cv,cu;
354
355    register int col, row, x, y;
356
357    if(!md_running()){
358        return 0;
359    }
360
361    tick=get_tick_count();
362#ifdef OPT_MD_DEBUG
363    if(motion_detector->comp_calls_cnt < MD_REC_CALLS_CNT) {
364        motion_detector->comp_calls[motion_detector->comp_calls_cnt]=tick;
365    }
366    motion_detector->comp_calls_cnt++;
367#endif
368
369    if(motion_detector->start_time + motion_detector->timeout < tick ) {
370        md_save_calls_history();
371        motion_detector->running = 0;
372        return 0;
373    }
374
375    if(motion_detector->last_measure_time + motion_detector->measure_interval > tick){
376        // wait for the next time
377        return 1;
378    }
379
380    motion_detector->last_measure_time=tick;
381
382// swap pointers so we don't need to copy last data array into Previous one
383    tmp=motion_detector->curr;
384    motion_detector->curr=motion_detector->prev;
385    motion_detector->prev=tmp;
386
387     img = vid_get_viewport_live_fb();
388        if(img==NULL){
389            img = vid_get_viewport_fb();
390        }
391
392#ifdef OPT_MD_DEBUG
393    if(motion_detector->comp_calls_cnt==50 && (motion_detector->parameters & MD_MAKE_RAM_DUMP_FILE) != 0 ){
394        mx_dump_memory((char*)img);
395    }
396#endif
397
398        vp_h = vid_get_viewport_height();
399        vp_w = vid_get_viewport_byte_width() * vid_get_viewport_yscale();
400        img += vid_get_viewport_image_offset();         // offset into viewport for when image size != viewport size (e.g. 16:9 image on 4:3 LCD)
401
402        x_step=(vid_get_viewport_width()*3)/motion_detector->columns;
403        y_step=vp_h/motion_detector->rows;
404
405    for (idx=0, row=0; row < motion_detector->rows; row++)
406    {
407        for (col=0; col < motion_detector->columns; col++, idx++)
408        {
409            // clear cur and points, previously down in it's own loop
410            // might be able to avoid clearing all, since some are overwritten below
411            motion_detector->points[idx] = 0;
412            motion_detector->curr[idx] = 0;
413
414            in_clipping_region=0;
415
416            if (col+1 >= motion_detector->clipping_region_column1 &&
417                col+1 <= motion_detector->clipping_region_column2 &&
418                row+1 >= motion_detector->clipping_region_row1 &&
419                row+1 <= motion_detector->clipping_region_row2)
420            {
421                in_clipping_region=1;
422            }
423
424            if (
425                (motion_detector->clipping_region_mode==MD_REGION_NONE) ||
426                (motion_detector->clipping_region_mode==MD_REGION_EXCLUDE && in_clipping_region==0) ||
427                (motion_detector->clipping_region_mode==MD_REGION_INCLUDE && in_clipping_region==1)
428               )
429            {
430                x_end=(col+1)*x_step;
431                                y_end=(row+1)*y_step*vp_w;
432                                for(y=row*y_step*vp_w; y<y_end; y+=motion_detector->pixels_step*vp_w){
433                                        for(x=col*x_step; x<x_end; x+=motion_detector->pixels_step*3){
434
435                                                // ARRAY of UYVYYY values
436                        // 6 bytes - 4 pixels
437
438                        switch(motion_detector->pixel_measure_mode){
439                                                default:
440                                                case MD_MEASURE_MODE_Y:
441                                                        val = img[y + x + 1];                           //Y
442                                                        break;
443                                                case MD_MEASURE_MODE_U:
444                                                        val = img[y + (x&0xFFFFFFFE)];          //U
445                                                        break;
446                                                case MD_MEASURE_MODE_V:
447                                                        val = img[y + (x&0xFFFFFFFE) + 2];      //V
448                                                        break;
449
450                                                case MD_MEASURE_MODE_R:
451                                                        cy=img[y + x + 1];
452                                                        cv=img[y + (x&0xFFFFFFFE) + 2];
453                                                        val = clip(((cy<<12)           + cv*5743 + 2048)>>12); // R
454                                                        break;
455
456                                                case MD_MEASURE_MODE_G:
457                                                        cy=img[y + x + 1];
458                                                        cu=img[y + (x&0xFFFFFFFE)];
459                                                        cv=img[y + (x&0xFFFFFFFE) + 2];
460                                                        val = clip(((cy<<12) - cu*1411 - cv*2925 + 2048)>>12); // G
461                                                        break;
462
463                                                case MD_MEASURE_MODE_B:
464                                                        cy=img[y + x + 1];
465                                                        cu=img[y + (x&0xFFFFFFFE)];
466                                                        val = clip(((cy<<12) + cu*7258           + 2048)>>12); // B
467                                                        break;
468                        }
469
470                        motion_detector->curr[ idx ] += val;
471                        motion_detector->points[ idx ]++;
472                    }
473                }
474            }
475        }
476    }
477    // << fill "curr" array
478
479
480    if(motion_detector->previous_picture_is_ready==0){
481        motion_detector->previous_picture_is_ready=1;
482        motion_detector->start_time=get_tick_count();
483        motion_detector->last_measure_time=motion_detector->start_time-motion_detector->measure_interval;
484        return 1;
485    }
486
487
488    // >> compare arrays here
489    for (idx=0; idx < motion_detector->rows*motion_detector->columns; idx++)
490    {
491        tmp2=0;
492        if(motion_detector->points[idx]>0){
493            motion_detector->prev[idx] = (motion_detector->curr[idx]-motion_detector->prev[idx])/motion_detector->points[idx];
494            tmp2 = ( motion_detector->prev[idx] < 0 ) ? -motion_detector->prev[idx] : motion_detector->prev[idx] ;
495        }
496
497        if( tmp2 > motion_detector->threshold ){
498            if (motion_detector->start_time+motion_detector->msecs_before_trigger < tick){
499                motion_detector->detected_cells++;
500            }
501        }
502    }
503    // << compare arrays here
504
505
506    if( motion_detector->detected_cells > 0 ){
507//      sprintf(buf,"-cells: %d", motion_detector->detected_cells);
508//      script_console_add_line(buf);
509
510        if (motion_detector->start_time+motion_detector->msecs_before_trigger < tick){
511            motion_detector->running=0;
512            motion_detector->return_value = motion_detector->detected_cells;
513
514//          md_save_calls_history();
515            if( ( motion_detector->parameters&MD_DO_IMMEDIATE_SHOOT ) !=0){
516                //make shoot
517                md_kbd_sched_immediate_shoot(motion_detector->parameters&MD_NO_SHUTTER_RELEASE_ON_SHOOT);
518            }
519            return 0;
520        }
521    }
522
523
524    return 1;
525}
526
527
528int md_get_cell_diff(int column, int row){
529
530    if(!motion_detector){
531        return 0;
532    }
533    if (column<1 || column > motion_detector->columns){
534        return 0;
535    }
536
537    if (row<1 || row > motion_detector->rows ){
538        return 0;
539    }
540
541
542    return motion_detector->prev[ MD_XY2IDX(column-1,row-1) ];
543}
544
545
546void md_draw_grid(){
547    int x_step, y_step, col, row;
548    int xoffset, yoffset;
549    int do_draw_rect, i, tmp2, in_clipping_region, color, col_start, col_stop, row_start, row_stop;
550
551    if(!md_running() || motion_detector->draw_grid==0){
552        return ;
553        }
554
555        xoffset = vid_get_viewport_xoffset();   // used when image size != viewport size
556        yoffset = vid_get_viewport_yoffset();   // used when image size != viewport size
557
558        x_step=(camera_screen.width-xoffset*2)/motion_detector->columns;
559        y_step=(camera_screen.height-yoffset*2)/motion_detector->rows;
560#if 0
561        row_start=1;
562        row_stop=motion_detector->rows;
563        if(motion_detector->clipping_region_mode==2){
564                row_start=motion_detector->clipping_region_row1;
565                row_stop=motion_detector->clipping_region_row2+1;
566                col_start=motion_detector->clipping_region_column1;
567                col_stop=motion_detector->clipping_region_column2+1;
568        }
569        if(motion_detector->clipping_region_mode==0 || motion_detector->clipping_region_mode==2){
570                for(col=col_start;col<col_stop;col++){
571                        draw_line(col*x_step,0,col*x_step,camera_screen.height, COLOR_GREEN);
572                }
573                for(row=row_start;row<row_stop;row++){
574                        draw_line(0,row*y_step,camera_screen.width,row*y_step, COLOR_GREEN);
575                }
576        } else if(motion_detector->clipping_region_mode==1){
577                for(col=1;col<motion_detector->columns;col++){
578                        if(col<){
579                                draw_line(col*x_step,0,col*x_step,camera_screen.height, COLOR_GREEN);
580                        }
581                }
582        }
583#endif
584
585    for ( col=0, row=0; row < motion_detector->rows; ){
586        i=MD_XY2IDX(col,row);
587        tmp2 = ( motion_detector->prev[i] < 0 ) ? -motion_detector->prev[i] : motion_detector->prev[i] ;
588
589            in_clipping_region=0;
590            if ( col+1>=motion_detector->clipping_region_column1
591                && col+1<=motion_detector->clipping_region_column2
592                && row+1>=motion_detector->clipping_region_row1
593                && row+1<=motion_detector->clipping_region_row2
594                ){
595                    in_clipping_region=1;
596            }
597
598            do_draw_rect=0;
599
600            if(motion_detector->clipping_region_mode==MD_REGION_EXCLUDE && in_clipping_region==0){
601                do_draw_rect=1;
602            }
603
604            if(motion_detector->clipping_region_mode==MD_REGION_INCLUDE && in_clipping_region==1){
605                do_draw_rect=1;
606            }
607
608            if(motion_detector->clipping_region_mode==MD_REGION_NONE){
609                do_draw_rect=1;
610            }
611
612            if(do_draw_rect==1){
613                color=COLOR_HISTO_G;
614                if( tmp2 > motion_detector->threshold){
615                    color=COLOR_RED;
616                }
617                draw_rect(xoffset+x_step*col+2,yoffset+y_step*row+2, xoffset+x_step*(col+1)-2, yoffset+y_step*(row+1)-2,color);
618            }
619
620            col++;
621            if( col >= motion_detector->columns ){
622                row++;
623                col=0;
624            }
625    }
626
627}
628
629int md_get_result()
630{
631    return motion_detector->return_value;
632}
633
634
635// =========  MODULE INIT =================
636
637#include "module_load.h"
638int module_idx=-1;
639
640/***************** BEGIN OF AUXILARY PART *********************
641  ATTENTION: DO NOT REMOVE OR CHANGE SIGNATURES IN THIS SECTION
642 **************************************************************/
643
644struct libmotiondetect_sym libmotiondetect = {
645                        MAKE_API_VERSION(1,0),          // apiver: increase major if incompatible changes made in module,
646                                                                                // increase minor if compatible changes made(including extending this struct)
647
648        md_close_motion_detector,
649        md_init_motion_detector,
650        md_detect_motion,
651        md_get_cell_diff,
652        md_draw_grid,
653        md_get_result
654        };
655
656
657void* MODULE_EXPORT_LIST[] = {
658        /* 0 */ (void*)EXPORTLIST_MAGIC_NUMBER,
659        /* 1 */ (void*)1,
660
661                        &libmotiondetect
662                };
663
664
665//---------------------------------------------------------
666// PURPOSE:   Bind module symbols with chdk.
667//              Required function
668// PARAMETERS: pointer to chdk list of export
669// RETURN VALUE: 1 error, 0 ok
670//---------------------------------------------------------
671int _module_loader( unsigned int* chdk_export_list )
672{
673  if ( chdk_export_list[0] != EXPORTLIST_MAGIC_NUMBER )
674     return 1;
675
676  if ( !API_VERSION_MATCH_REQUIREMENT( camera_sensor.api_version, 1, 0 ) )
677         return 1;
678
679  return 0;
680}
681
682
683//---------------------------------------------------------
684// PURPOSE: Finalize module operations (close allocs, etc)
685// RETURN VALUE: 0-ok, 1-fail
686//---------------------------------------------------------
687int _module_unloader()
688{
689    md_close_motion_detector();
690    return 0;
691}
692
693
694//---------------------------------------------------------
695// PURPOSE: Default action for simple modules (direct run)
696// NOTE: Please comment this function if no default action and this library module
697//---------------------------------------------------------
698int _module_run(int moduleidx, int argn, int* arguments)
699{
700  module_idx=moduleidx;
701
702  return 0;
703}
704
705/******************** Module Information structure ******************/
706
707struct ModuleInfo _module_info = {      MODULEINFO_V1_MAGICNUM,
708                                                                        sizeof(struct ModuleInfo),
709
710                                                                        ANY_CHDK_BRANCH, 0,                     // Requirements of CHDK version
711                                                                        ANY_PLATFORM_ALLOWED,           // Specify platform dependency
712                                                                        MODULEINFO_FLAG_SYSTEM,         // flag
713                                                                        (int32_t)"Motion Detect (dll)",// Module name
714                                                                        1, 0,                                           // Module version
715                                                                        (int32_t)"Motion Detect"
716                                                                 };
717
718
719/*************** END OF AUXILARY PART *******************/
720
Note: See TracBrowser for help on using the repository browser.