[IMP] point_of_sale, hw_scale: first commit for the hw_scale module, which handles...
[odoo/odoo.git] / addons / point_of_sale / static / src / js / devices.js
1
2 function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
3
4     // the JobQueue schedules a sequence of 'jobs'. each job is
5     // a function returning a deferred. the queue waits for each job to finish 
6     // before launching the next. Each job can also be scheduled with a delay. 
7     // the  is used to prevent parallel requests to the proxy.
8
9     module.JobQueue = function(){
10         var queue = [];
11         var running = false;
12         var scheduled_end_time = 0;
13         var end_of_queue = (new $.Deferred()).resolve();
14         var stoprepeat = false;
15
16         var run = function(){
17             if(end_of_queue.state() === 'resolved'){
18                 end_of_queue =  new $.Deferred();
19             }
20             if(queue.length > 0){
21                 running = true;
22                 var job = queue[0];
23                 if(!job.opts.repeat || stoprepeat){
24                     queue.shift();
25                     stoprepeat = false;
26                 }
27
28                 // the time scheduled for this job
29                 scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0);
30
31                 // we run the job and put in def when it finishes
32                 var def = job.fun() || (new $.Deferred()).resolve();
33                 
34                 // we don't care if a job fails ... 
35                 def.always(function(){
36                     // we run the next job after the scheduled_end_time, even if it finishes before
37                     setTimeout(function(){
38                         run();
39                     }, Math.max(0, scheduled_end_time - (new Date()).getTime()) ); 
40                 });
41             }else{
42                 running = false;
43                 scheduled_end_time = 0;
44                 end_of_queue.resolve();
45             }
46         };
47         
48         // adds a job to the schedule.
49         // opts : {
50         //    duration    : the job is guaranteed to finish no quicker than this (milisec)
51         //    repeat      : if true, the job will be endlessly repeated
52         //    important   : if true, the scheduled job cannot be canceled by a queue.clear()
53         // }
54         this.schedule  = function(fun, opts){
55             queue.push({fun:fun, opts:opts || {}});
56             if(!running){
57                 run();
58             }
59         }
60
61         // remove all jobs from the schedule (except the ones marked as important)
62         this.clear = function(){
63             queue = _.filter(queue,function(job){job.opts.important === true}); 
64         };
65
66         // end the repetition of the current job
67         this.stoprepeat = function(){
68             stoprepeat = true;
69         };
70         
71         // returns a deferred that resolves when all scheduled 
72         // jobs have been run.
73         // ( jobs added after the call to this method are considered as well )
74         this.finished = function(){
75             return end_of_queue;
76         }
77
78     };
79
80     // this object interfaces with the local proxy to communicate to the various hardware devices
81     // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
82     // methods are used both to signal an event, and to fetch information. 
83
84     module.ProxyDevice  = instance.web.Class.extend(openerp.PropertiesMixin,{
85         init: function(parent,options){
86             openerp.PropertiesMixin.init.call(this,parent);
87             var self = this;
88             options = options || {};
89             url = options.url || 'http://localhost:8069';
90             
91             this.weighting = false;
92             this.debug_weight = 0;
93             this.use_debug_weight = false;
94
95             this.paying = false;
96             this.default_payment_status = {
97                 status: 'waiting',
98                 message: '',
99                 payment_method: undefined,
100                 receipt_client: undefined,
101                 receipt_shop:   undefined,
102             };    
103             this.custom_payment_status = this.default_payment_status;
104
105             this.receipt_queue = [];
106
107             this.notifications = {};
108             this.bypass_proxy = false;
109
110             this.connection = null; 
111             this.host       = '';
112             this.keptalive  = false;
113
114             this.set('status',{});
115
116             this.set_connection_status('disconnected');
117
118             this.on('change:status',this,function(eh,status){
119                 status = status.newValue;
120                 if(status.status === 'connected'){
121                     self.print_receipt();
122                 }
123             });
124
125             window.hw_proxy = this;
126         },
127         set_connection_status: function(status,drivers){
128             oldstatus = this.get('status');
129             newstatus = {};
130             newstatus.status = status;
131             newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers;
132             newstatus.drivers = drivers ? drivers : newstatus.drivers;
133             this.set('status',newstatus);
134         },
135         disconnect: function(){
136             if(this.get('status').status !== 'disconnected'){
137                 this.connection.destroy();
138                 this.set_connection_status('disconnected');
139             }
140         },
141
142         // connects to the specified url
143         connect: function(url){
144             var self = this;
145             this.connection = new instance.web.Session(undefined,url, { use_cors: true});
146             this.host   = url;
147             this.set_connection_status('connecting',{});
148
149             return this.message('handshake').then(function(response){
150                     if(response){
151                         self.set_connection_status('connected');
152                         localStorage['hw_proxy_url'] = url;
153                         self.keepalive();
154                     }else{
155                         self.set_connection_status('disconnected');
156                         console.error('Connection refused by the Proxy');
157                     }
158                 },function(){
159                     self.set_connection_status('disconnected');
160                     console.error('Could not connect to the Proxy');
161                 });
162         },
163
164         // find a proxy and connects to it. for options see find_proxy
165         //   - force_ip : only try to connect to the specified ip. 
166         //   - port: what port to listen to (default 8069)
167         //   - progress(fac) : callback for search progress ( fac in [0,1] ) 
168         autoconnect: function(options){
169             var self = this;
170             this.set_connection_status('connecting',{});
171             var found_url = new $.Deferred();
172             var success = new $.Deferred();
173
174             if ( options.force_ip ){
175                 // if the ip is forced by server config, bailout on fail
176                 found_url = this.try_hard_to_connect(options.force_ip, options)
177             }else if( localStorage['hw_proxy_url'] ){
178                 // try harder when we remember a good proxy url
179                 found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
180                     .then(null,function(){
181                         return self.find_proxy(options);
182                     });
183             }else{
184                 // just find something quick
185                 found_url = this.find_proxy(options);
186             }
187
188             success = found_url.then(function(url){
189                     return self.connect(url);
190                 });
191
192             success.fail(function(){
193                 self.set_connection_status('disconnected');
194             });
195
196             return success;
197         },
198
199         // starts a loop that updates the connection status
200         keepalive: function(){
201             var self = this;
202             if(!this.keptalive){
203                 this.keptalive = true;
204                 function status(){
205                     self.connection.rpc('/hw_proxy/status_json',{},{timeout:2500})       
206                         .then(function(driver_status){
207                             self.set_connection_status('connected',driver_status);
208                         },function(){
209                             if(self.get('status').status !== 'connecting'){
210                                 self.set_connection_status('disconnected');
211                             }
212                         }).always(function(){
213                             setTimeout(status,5000);
214                         });
215                 }
216                 status();
217             };
218         },
219
220         message : function(name,params){
221             var callbacks = this.notifications[name] || [];
222             for(var i = 0; i < callbacks.length; i++){
223                 callbacks[i](params);
224             }
225             if(this.get('status').status !== 'disconnected'){
226                 return this.connection.rpc('/hw_proxy/' + name, params || {});       
227             }else{
228                 return (new $.Deferred()).reject();
229             }
230         },
231
232         // try several time to connect to a known proxy url
233         try_hard_to_connect: function(url,options){
234             options   = options || {};
235             var port  = ':' + (options.port || '8069');
236
237             this.set_connection_status('connecting');
238
239             if(url.indexOf('//') < 0){
240                 url = 'http://'+url;
241             }
242
243             if(url.indexOf(':',5) < 0){
244                 url = url+port;
245             }
246
247             // try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
248             function try_real_hard_to_connect(url, retries, done){
249
250                 done = done || new $.Deferred();
251
252                 var c = $.ajax({
253                     url: url + '/hw_proxy/hello',
254                     method: 'GET',
255                     timeout: 1000,
256                 })
257                 .done(function(){
258                     done.resolve(url);
259                 })
260                 .fail(function(){
261                     if(retries > 0){
262                         try_real_hard_to_connect(url,retries-1,done);
263                     }else{
264                         done.reject();
265                     }
266                 });
267                 return done;
268             }
269
270             return try_real_hard_to_connect(url,3);
271         },
272
273         // returns as a deferred a valid host url that can be used as proxy.
274         // options:
275         //   - port: what port to listen to (default 8069)
276         //   - progress(fac) : callback for search progress ( fac in [0,1] ) 
277         find_proxy: function(options){
278             options = options || {};
279             var self  = this;
280             var port  = ':' + (options.port || '8069');
281             var urls  = [];
282             var found = false;
283             var parallel = 8;
284             var done = new $.Deferred(); // will be resolved with the proxies valid urls
285             var threads  = [];
286             var progress = 0;
287
288
289             urls.push('http://localhost'+port);
290             for(var i = 0; i < 256; i++){
291                 urls.push('http://192.168.0.'+i+port);
292                 urls.push('http://192.168.1.'+i+port);
293                 urls.push('http://10.0.0.'+i+port);
294             }
295
296             var prog_inc = 1/urls.length; 
297
298             function update_progress(){
299                 progress = found ? 1 : progress + prog_inc;
300                 if(options.progress){
301                     options.progress(progress);
302                 }
303             }
304
305             function thread(done){
306                 var url = urls.shift();
307
308                 done = done || new $.Deferred();
309
310                 if( !url || found || !self.searching_for_proxy ){ 
311                     done.resolve();
312                     return done;
313                 }
314
315                 var c = $.ajax({
316                         url: url + '/hw_proxy/hello',
317                         method: 'GET',
318                         timeout: 400, 
319                     }).done(function(){
320                         found = true;
321                         update_progress();
322                         done.resolve(url);
323                     })
324                     .fail(function(){
325                         update_progress();
326                         thread(done);
327                     });
328
329                 return done;
330             }
331
332             this.searching_for_proxy = true;
333
334             for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
335                 threads.push(thread());
336             }
337             
338             $.when.apply($,threads).then(function(){
339                 var urls = [];
340                 for(var i = 0; i < arguments.length; i++){
341                     if(arguments[i]){
342                         urls.push(arguments[i]);
343                     }
344                 }
345                 done.resolve(urls[0]);
346             });
347
348             return done;
349         },
350
351         stop_searching: function(){
352             this.searching_for_proxy = false;
353             this.set_connection_status('disconnected');
354         },
355
356         // this allows the client to be notified when a proxy call is made. The notification 
357         // callback will be executed with the same arguments as the proxy call
358         add_notification: function(name, callback){
359             if(!this.notifications[name]){
360                 this.notifications[name] = [];
361             }
362             this.notifications[name].push(callback);
363         },
364         
365         //a product has been scanned and recognized with success
366         // ean is a parsed ean object
367         scan_item_success: function(ean){
368             return this.message('scan_item_success',{ean: ean});
369         },
370
371         // a product has been scanned but not recognized
372         // ean is a parsed ean object
373         scan_item_error_unrecognized: function(ean){
374             return this.message('scan_item_error_unrecognized',{ean: ean});
375         },
376
377         //the client is asking for help
378         help_needed: function(){
379             return this.message('help_needed');
380         },
381
382         //the client does not need help anymore
383         help_canceled: function(){
384             return this.message('help_canceled');
385         },
386
387         // returns the weight on the scale. 
388         scale_read: function(){
389             var self = this;
390             var ret = new $.Deferred();
391             console.log('scale_read');
392             this.message('scale_read',{})
393                 .then(function(weight){
394                     console.log(weight)
395                     ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
396                 }, function(){ //failed to read weight
397                     ret.resolve(self.use_debug_weight ? self.debug_weight : {weight:0.0, info:'ok'});
398                 });
399             return ret;
400         },
401
402         // sets a custom weight, ignoring the proxy returned value. 
403         debug_set_weight: function(kg){
404             this.use_debug_weight = true;
405             this.debug_weight = kg;
406         },
407
408         // resets the custom weight and re-enable listening to the proxy for weight values
409         debug_reset_weight: function(){
410             this.use_debug_weight = false;
411             this.debug_weight = 0;
412         },
413
414
415         // the pos asks the client to pay 'price' units
416         payment_request: function(price){
417             var ret = new $.Deferred();
418             this.paying = true;
419             this.custom_payment_status = this.default_payment_status;
420             return this.message('payment_request',{'price':price});
421         },
422
423         payment_status: function(){
424             if(this.bypass_proxy){
425                 this.bypass_proxy = false;
426                 return (new $.Deferred()).resolve(this.custom_payment_status);
427             }else{
428                 return this.message('payment_status');
429             }
430         },
431         
432         // override what the proxy says and accept the payment
433         debug_accept_payment: function(){
434             this.bypass_proxy = true;
435             this.custom_payment_status = {
436                 status: 'paid',
437                 message: 'Successfull Payment, have a nice day',
438                 payment_method: 'AMEX',
439                 receipt_client: '<xml>bla</xml>',
440                 receipt_shop:   '<xml>bla</xml>',
441             };    
442         },
443
444         // override what the proxy says and reject the payment
445         debug_reject_payment: function(){
446             this.bypass_proxy = true;
447             this.custom_payment_status = {
448                 status: 'error-rejected',
449                 message: 'Sorry you don\'t have enough money :(',
450             };    
451         },
452         // the client cancels his payment
453         payment_cancel: function(){
454             this.paying = false;
455             this.custom_payment_status = 'waiting_for_payment';
456             return this.message('payment_cancel');
457         },
458
459         // called when the client logs in or starts to scan product
460         transaction_start: function(){
461             return this.message('transaction_start');
462         },
463
464         // called when the clients has finished his interaction with the machine
465         transaction_end: function(){
466             return this.message('transaction_end');
467         },
468
469         // called when the POS turns to cashier mode
470         cashier_mode_activated: function(){
471             return this.message('cashier_mode_activated');
472         },
473
474         // called when the POS turns to client mode
475         cashier_mode_deactivated: function(){
476             return this.message('cashier_mode_deactivated');
477         },
478         
479         // ask for the cashbox (the physical box where you store the cash) to be opened
480         open_cashbox: function(){
481             return this.message('open_cashbox');
482         },
483
484         /* 
485          * ask the printer to print a receipt
486          */
487         print_receipt: function(receipt){
488             var self = this;
489             if(receipt){
490                 this.receipt_queue.push(receipt);
491             }
492             var aborted = false;
493             function send_printing_job(){
494                 if (self.receipt_queue.length > 0){
495                     var r = self.receipt_queue.shift();
496                     self.message('print_xml_receipt',{ receipt: r },{ timeout: 5000 })
497                         .then(function(){
498                             send_printing_job();
499                         },function(){
500                             self.receipt_queue.unshift(r)
501                         });
502                 }
503             }
504             send_printing_job();
505         },
506
507         // asks the proxy to log some information, as with the debug.log you can provide several arguments.
508         log: function(){
509             return this.message('log',{'arguments': _.toArray(arguments)});
510         },
511
512         // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) 
513         print_pdf_invoice: function(pdfinvoice){
514             return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});
515         },
516     });
517
518     // this module interfaces with the barcode reader. It assumes the barcode reader
519     // is set-up to act like  a keyboard. Use connect() and disconnect() to activate 
520     // and deactivate the barcode reader. Use set_action_callbacks to tell it
521     // what to do when it reads a barcode.
522     module.BarcodeReader = instance.web.Class.extend({
523         actions:[
524             'product',
525             'cashier',
526             'client',
527             'discount',
528         ],
529         init: function(attributes){
530             this.pos = attributes.pos;
531             this.action_callback = {};
532             this.proxy = attributes.proxy;
533             this.remote_scanning = false;
534             this.remote_active = 0;
535
536             this.action_callback_stack = [];
537
538             this.weight_prefix_set   = attributes.weight_prefix_set   ||  {'21':''};
539             this.discount_prefix_set = attributes.discount_prefix_set ||  {'22':''};
540             this.price_prefix_set    = attributes.price_prefix_set    ||  {'23':''};
541             this.cashier_prefix_set  = attributes.cashier_prefix_set  ||  {'041':''};
542             this.client_prefix_set   = attributes.client_prefix_set   ||  {'042':''};
543
544         },
545
546         save_callbacks: function(){
547             var callbacks = {};
548             for(name in this.action_callback){
549                 callbacks[name] = this.action_callback[name];
550             }
551             this.action_callback_stack.push(callbacks);
552         },
553
554         restore_callbacks: function(){
555             if(this.action_callback_stack.length){
556                 var callbacks = this.action_callback_stack.pop();
557                 this.action_callback = callbacks;
558             }
559         },
560        
561         // when an ean is scanned and parsed, the callback corresponding
562         // to its type is called with the parsed_ean as a parameter. 
563         // (parsed_ean is the result of parse_ean(ean)) 
564         // 
565         // callbacks is a Map of 'actions' : callback(parsed_ean)
566         // that sets the callback for each action. if a callback for the
567         // specified action already exists, it is replaced. 
568         // 
569         // possible actions include : 
570         // 'product' | 'cashier' | 'client' | 'discount' 
571     
572         set_action_callback: function(action, callback){
573             if(arguments.length == 2){
574                 this.action_callback[action] = callback;
575             }else{
576                 var actions = arguments[0];
577                 for(action in actions){
578                     this.set_action_callback(action,actions[action]);
579                 }
580             }
581         },
582
583         //remove all action callbacks 
584         reset_action_callbacks: function(){
585             for(action in this.action_callback){
586                 this.action_callback[action] = undefined;
587             }
588         },
589         // returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
590         ean_checksum: function(ean){
591             var code = ean.split('');
592             if(code.length !== 13){
593                 return -1;
594             }
595             var oddsum = 0, evensum = 0, total = 0;
596             code = code.reverse().splice(1);
597             for(var i = 0; i < code.length; i++){
598                 if(i % 2 == 0){
599                     oddsum += Number(code[i]);
600                 }else{
601                     evensum += Number(code[i]);
602                 }
603             }
604             total = oddsum * 3 + evensum;
605             return Number((10 - total % 10) % 10);
606         },
607         // returns true if the ean is a valid EAN codebar number by checking the control digit.
608         // ean must be a string
609         check_ean: function(ean){
610             return /^\d+$/.test(ean) && this.ean_checksum(ean) === Number(ean[ean.length-1]);
611         },
612         // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
613         sanitize_ean:function(ean){
614             ean = ean.substr(0,13);
615
616             for(var n = 0, count = (13 - ean.length); n < count; n++){
617                 ean = ean + '0';
618             }
619             return ean.substr(0,12) + this.ean_checksum(ean);
620         },
621         
622         // attempts to interpret an ean (string encoding an ean)
623         // it will check its validity then return an object containing various
624         // information about the ean.
625         // most importantly : 
626         // - code    : the ean
627         // - type   : the type of the ean: 
628         //      'price' |  'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
629         //
630         // - prefix : the prefix that has ben used to determine the type
631         // - id     : the part of the ean that identifies something
632         // - value  : if the id encodes a numerical value, it will be put there
633         // - unit   : if the encoded value has a unit, it will be put there. 
634         //            not to be confused with the 'unit' type, which represent an unit of a 
635         //            unique product
636         // - base_code : the ean code with all the encoding parts set to zero; the one put on
637         //               the product in the backend
638
639         parse_ean: function(ean){
640             var parse_result = {
641                 encoding: 'ean13',
642                 type:'unknown',  
643                 prefix:'',
644                 code:ean,
645                 base_code: ean,
646                 id:'',
647                 value: 0,
648                 unit: 'none',
649             };
650
651             function match_prefix(prefix_set, type){
652                 for(prefix in prefix_set){
653                     if(ean.substring(0,prefix.length) === prefix){
654                         parse_result.prefix = prefix;
655                         parse_result.type = type;
656                         return true;
657                     }
658                 }
659                 return false;
660             }
661
662             if (!this.check_ean(ean)){
663                 parse_result.type = 'error';
664             } else if( match_prefix(this.price_prefix_set,'price')){
665                 parse_result.id = ean.substring(0,7);
666                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
667                 parse_result.value = Number(ean.substring(7,12))/100.0;
668                 parse_result.unit  = 'euro';
669             } else if( match_prefix(this.weight_prefix_set,'weight')){
670                 parse_result.id = ean.substring(0,7);
671                 parse_result.value = Number(ean.substring(7,12))/1000.0;
672                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
673                 parse_result.unit = 'Kg';
674             } else if( match_prefix(this.client_prefix_set,'client')){
675                 parse_result.id = ean.substring(0,7);
676                 parse_result.unit = 'Kg';
677             } else if( match_prefix(this.cashier_prefix_set,'cashier')){
678                 parse_result.id = ean.substring(0,7);
679             } else if( match_prefix(this.discount_prefix_set,'discount')){
680                 parse_result.id    = ean.substring(0,7);
681                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
682                 parse_result.value = Number(ean.substring(7,12))/100.0;
683                 parse_result.unit  = '%';
684             } else {
685                 parse_result.type = 'unit';
686                 parse_result.prefix = '';
687                 parse_result.id = ean;
688             }
689             return parse_result;
690         },
691         
692         scan: function(code){
693             if(code.length < 3){
694                 return;
695             }else if(code.length === 13 && this.check_ean(code)){
696                 var parse_result = this.parse_ean(code);
697             }else if(code.length === 12 && this.check_ean('0'+code)){
698                 // many barcode scanners strip the leading zero of ean13 barcodes.
699                 // This is because ean-13 are UCP-A with an additional zero at the beginning,
700                 // so by stripping zeros you get retrocompatibility with UCP-A systems.
701                 var parse_result = this.parse_ean('0'+code);
702             }else if(this.pos.db.get_product_by_reference(code)){
703                 var parse_result = {
704                     encoding: 'reference',
705                     type: 'unit',
706                     code: code,
707                     prefix: '',
708                 };
709             }else{
710                 var parse_result = {
711                     encoding: 'error',
712                     type: 'error',
713                     code: code,
714                     prefix: '',
715                 };
716                 return;
717             }
718
719             if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){    //ean is associated to a product
720                 if(this.action_callback['product']){
721                     this.action_callback['product'](parse_result);
722                 }
723             }else{
724                 if(this.action_callback[parse_result.type]){
725                     this.action_callback[parse_result.type](parse_result);
726                 }
727             }
728         },
729
730         // starts catching keyboard events and tries to interpret codebar 
731         // calling the callbacks when needed.
732         connect: function(){
733
734             var self = this;
735             var code = "";
736             var timeStamp  = 0;
737             var onlynumbers = true;
738             var timeout = null;
739
740             this.handler = function(e){
741
742                 if(e.which === 13){ //ignore returns
743                     e.preventDefault();
744                     return;
745                 }
746
747                 if(timeStamp + 50 < new Date().getTime()){
748                     code = "";
749                     onlynumbers = true;
750                 }
751
752                 timeStamp = new Date().getTime();
753                 clearTimeout(timeout);
754
755                 if( e.which < 48 || e.which >= 58 ){ // not a number
756                     onlynumbers = false;
757                 }
758
759                 code += String.fromCharCode(e.which);
760
761                 // we wait for a while after the last input to be sure that we are not mistakingly
762                 // returning a code which is a prefix of a bigger one :
763                 // Internal Ref 5449 vs EAN13 5449000...
764
765                 timeout = setTimeout(function(){
766                     self.scan(code);
767                     code = "";
768                     onlynumbers = true;
769                 },100);
770             };
771
772             $('body').on('keypress', this.handler);
773         },
774
775         // stops catching keyboard events 
776         disconnect: function(){
777             $('body').off('keypress', this.handler)
778         },
779
780         // the barcode scanner will listen on the hw_proxy/scanner interface for 
781         // scan events until disconnect_from_proxy is called
782         connect_to_proxy: function(){ 
783             var self = this;
784             this.remote_scanning = true;
785             if(this.remote_active >= 1){
786                 return;
787             }
788             this.remote_active = 1;
789
790             function waitforbarcode(){
791                 return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
792                     .then(function(barcode){
793                         if(!self.remote_scanning){ 
794                             self.remote_active = 0;
795                             return; 
796                         }
797                         self.scan(barcode);
798                         waitforbarcode();
799                     },
800                     function(){
801                         if(!self.remote_scanning){
802                             self.remote_active = 0;
803                             return;
804                         }
805                         setTimeout(waitforbarcode,5000);
806                     });
807             }
808             waitforbarcode();
809         },
810
811         // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
812         disconnect_from_proxy: function(){
813             this.remote_scanning = false;
814         },
815     });
816
817 }