summaryrefslogtreecommitdiff
path: root/addons/point_of_sale/static/src/js/devices.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/point_of_sale/static/src/js/devices.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/point_of_sale/static/src/js/devices.js')
-rw-r--r--addons/point_of_sale/static/src/js/devices.js492
1 files changed, 492 insertions, 0 deletions
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
new file mode 100644
index 00000000..a4a80a9c
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -0,0 +1,492 @@
+odoo.define('point_of_sale.devices', function (require) {
+"use strict";
+
+var core = require('web.core');
+var mixins = require('web.mixins');
+var Session = require('web.Session');
+var Printer = require('point_of_sale.Printer').Printer;
+
+// the JobQueue schedules a sequence of 'jobs'. each job is
+// a function returning a promise. The queue waits for each job to finish
+// before launching the next. Each job can also be scheduled with a delay.
+// the is used to prevent parallel requests to the proxy.
+
+var JobQueue = function(){
+ var queue = [];
+ var running = false;
+ var scheduled_end_time = 0;
+ var end_of_queue = Promise.resolve();
+ var stoprepeat = false;
+
+ var run = function () {
+ var runNextJob = function () {
+ if (queue.length === 0) {
+ running = false;
+ scheduled_end_time = 0;
+ return Promise.resolve();
+ }
+ running = true;
+ var job = queue[0];
+ if (!job.opts.repeat || stoprepeat) {
+ queue.shift();
+ stoprepeat = false;
+ }
+
+ // the time scheduled for this job
+ scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0);
+
+ // we run the job and put in prom when it finishes
+ var prom = job.fun() || Promise.resolve();
+
+ var always = function () {
+ // we run the next job after the scheduled_end_time, even if it finishes before
+ return new Promise(function (resolve, reject) {
+ setTimeout(
+ resolve,
+ Math.max(0, scheduled_end_time - (new Date()).getTime())
+ );
+ });
+ };
+ // we don't care if a job fails ...
+ return prom.then(always, always).then(runNextJob);
+ };
+
+ if (!running) {
+ end_of_queue = runNextJob();
+ }
+ };
+
+ /**
+ * Adds a job to the schedule.
+ *
+ * @param {function} fun must return a promise
+ * @param {object} [opts]
+ * @param {number} [opts.duration] the job is guaranteed to finish no quicker than this (milisec)
+ * @param {boolean} [opts.repeat] if true, the job will be endlessly repeated
+ * @param {boolean} [opts.important] if true, the scheduled job cannot be canceled by a queue.clear()
+ */
+ this.schedule = function (fun, opts) {
+ queue.push({fun:fun, opts:opts || {}});
+ if(!running){
+ run();
+ }
+ };
+
+ // remove all jobs from the schedule (except the ones marked as important)
+ this.clear = function(){
+ queue = _.filter(queue,function(job){return job.opts.important === true;});
+ };
+
+ // end the repetition of the current job
+ this.stoprepeat = function(){
+ stoprepeat = true;
+ };
+
+ /**
+ * Returns a promise that resolves when all scheduled jobs have been run.
+ * (jobs added after the call to this method are considered as well)
+ *
+ * @returns {Promise}
+ */
+ this.finished = function () {
+ return end_of_queue;
+ };
+
+};
+
+
+// this object interfaces with the local proxy to communicate to the various hardware devices
+// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
+// methods are used both to signal an event, and to fetch information.
+
+var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
+ init: function(parent,options){
+ mixins.PropertiesMixin.init.call(this);
+ var self = this;
+ this.setParent(parent);
+ options = options || {};
+
+ this.pos = parent;
+
+ this.weighing = false;
+ this.debug_weight = 0;
+ this.use_debug_weight = false;
+
+ this.paying = false;
+ this.default_payment_status = {
+ status: 'waiting',
+ message: '',
+ payment_method: undefined,
+ receipt_client: undefined,
+ receipt_shop: undefined,
+ };
+ this.custom_payment_status = this.default_payment_status;
+
+ this.notifications = {};
+ this.bypass_proxy = false;
+
+ this.connection = null;
+ this.host = '';
+ this.keptalive = false;
+
+ this.set('status',{});
+
+ this.set_connection_status('disconnected');
+
+ this.on('change:status',this,function(eh,status){
+ status = status.newValue;
+ if(status.status === 'connected' && self.printer) {
+ self.printer.print_receipt();
+ }
+ });
+
+ this.posbox_supports_display = true;
+
+ window.hw_proxy = this;
+ },
+ set_connection_status: function(status, drivers, msg=''){
+ var oldstatus = this.get('status');
+ var newstatus = {};
+ newstatus.status = status;
+ newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers;
+ newstatus.drivers = drivers ? drivers : newstatus.drivers;
+ newstatus.msg = msg;
+ this.set('status',newstatus);
+ },
+ disconnect: function(){
+ if(this.get('status').status !== 'disconnected'){
+ this.connection.destroy();
+ this.set_connection_status('disconnected');
+ }
+ },
+
+ /**
+ * Connects to the specified url.
+ *
+ * @param {string} url
+ * @returns {Promise}
+ */
+ connect: function(url){
+ var self = this;
+ this.connection = new Session(undefined,url, { use_cors: true});
+ this.host = url;
+ if (this.pos.config.iface_print_via_proxy) {
+ this.connect_to_printer();
+ }
+ this.set_connection_status('connecting',{});
+
+ return this.message('handshake').then(function(response){
+ if(response){
+ self.set_connection_status('connected');
+ localStorage.hw_proxy_url = url;
+ self.keepalive();
+ }else{
+ self.set_connection_status('disconnected');
+ console.error('Connection refused by the Proxy');
+ }
+ },function(){
+ self.set_connection_status('disconnected');
+ console.error('Could not connect to the Proxy');
+ });
+ },
+
+ connect_to_printer: function () {
+ this.printer = new Printer(this.host, this.pos);
+ },
+
+ /**
+ * Find a proxy and connects to it.
+ *
+ * @param {Object} [options]
+ * @param {string} [options.force_ip] only try to connect to the specified ip.
+ * @param {string} [options.port] @see find_proxy
+ * @param {function} [options.progress] @see find_proxy
+ * @returns {Promise}
+ */
+ autoconnect: function (options) {
+ var self = this;
+ this.set_connection_status('connecting',{});
+ if (this.pos.config.iface_print_via_proxy) {
+ this.connect_to_printer();
+ }
+ var found_url = new Promise(function () {});
+
+ if (options.force_ip) {
+ // if the ip is forced by server config, bailout on fail
+ found_url = this.try_hard_to_connect(options.force_ip, options);
+ } else if (localStorage.hw_proxy_url) {
+ // try harder when we remember a good proxy url
+ found_url = this.try_hard_to_connect(localStorage.hw_proxy_url, options)
+ .catch(function () {
+ if (window.location.protocol != 'https:') {
+ return self.find_proxy(options);
+ }
+ });
+ } else {
+ // just find something quick
+ if (window.location.protocol != 'https:'){
+ found_url = this.find_proxy(options);
+ }
+ }
+
+ var successProm = found_url.then(function (url) {
+ return self.connect(url);
+ });
+
+ successProm.catch(function () {
+ self.set_connection_status('disconnected');
+ });
+
+ return successProm;
+ },
+
+ // starts a loop that updates the connection status
+ keepalive: function () {
+ var self = this;
+
+ function status(){
+ var always = function () {
+ setTimeout(status, 5000);
+ };
+ self.connection.rpc('/hw_proxy/status_json',{},{shadow: true, timeout:2500})
+ .then(function (driver_status) {
+ self.set_connection_status('connected',driver_status);
+ }, function () {
+ if(self.get('status').status !== 'connecting'){
+ self.set_connection_status('disconnected');
+ }
+ }).then(always, always);
+ }
+
+ if (!this.keptalive) {
+ this.keptalive = true;
+ status();
+ }
+ },
+
+ /**
+ * @param {string} name
+ * @param {Object} [params]
+ * @returns {Promise}
+ */
+ message : function (name, params) {
+ var callbacks = this.notifications[name] || [];
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](params);
+ }
+ if (this.get('status').status !== 'disconnected') {
+ return this.connection.rpc('/hw_proxy/' + name, params || {}, {shadow: true});
+ } else {
+ return Promise.reject();
+ }
+ },
+
+ /**
+ * Tries several time to connect to a known proxy url.
+ *
+ * @param {*} url
+ * @param {Object} [options]
+ * @param {string} [options.port=8069] what port to listen to
+ * @returns {Promise<string|Array>}
+ */
+ try_hard_to_connect: function (url, options) {
+ options = options || {};
+ var protocol = window.location.protocol;
+ var port = ( !options.port && protocol == "https:") ? ':443' : ':' + (options.port || '8069');
+
+ this.set_connection_status('connecting');
+
+ if(url.indexOf('//') < 0){
+ url = protocol + '//' + url;
+ }
+
+ if(url.indexOf(':',5) < 0){
+ url = url + port;
+ }
+
+ // try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
+ function try_real_hard_to_connect(url, retries) {
+ return Promise.resolve(
+ $.ajax({
+ url: url + '/hw_proxy/hello',
+ method: 'GET',
+ timeout: 1000,
+ })
+ .then(function () {
+ return Promise.resolve(url);
+ }, function (resp) {
+ if (retries > 0) {
+ return try_real_hard_to_connect(url, retries-1);
+ } else {
+ return Promise.reject([resp.statusText, url]);
+ }
+ })
+ );
+ }
+
+ return try_real_hard_to_connect(url, 3);
+ },
+
+ /**
+ * Returns as a promise a valid host url that can be used as proxy.
+ *
+ * @param {Object} [options]
+ * @param {string} [options.port] what port to listen to (default 8069)
+ * @param {function} [options.progress] callback for search progress ( fac in [0,1] )
+ * @returns {Promise<string>} will be resolved with the proxy valid url
+ */
+ find_proxy: function(options){
+ options = options || {};
+ var self = this;
+ var port = ':' + (options.port || '8069');
+ var urls = [];
+ var found = false;
+ var parallel = 8;
+ var threads = [];
+ var progress = 0;
+
+
+ urls.push('http://localhost'+port);
+ for(var i = 0; i < 256; i++){
+ urls.push('http://192.168.0.'+i+port);
+ urls.push('http://192.168.1.'+i+port);
+ urls.push('http://10.0.0.'+i+port);
+ }
+
+ var prog_inc = 1/urls.length;
+
+ function update_progress(){
+ progress = found ? 1 : progress + prog_inc;
+ if(options.progress){
+ options.progress(progress);
+ }
+ }
+
+ function thread () {
+ var url = urls.shift();
+
+ if (!url || found || !self.searching_for_proxy) {
+ return Promise.resolve();
+ }
+
+ return Promise.resolve(
+ $.ajax({
+ url: url + '/hw_proxy/hello',
+ method: 'GET',
+ timeout: 400,
+ }).then(function () {
+ found = true;
+ update_progress();
+ return Promise.resolve(url);
+ }, function () {
+ update_progress();
+ return thread();
+ })
+ );
+ }
+
+ this.searching_for_proxy = true;
+
+ var len = Math.min(parallel, urls.length);
+ for(i = 0; i < len; i++){
+ threads.push(thread());
+ }
+
+ return new Promise(function (resolve, reject) {
+ Promise.all(threads).then(function(results){
+ var urls = [];
+ for(var i = 0; i < results.length; i++){
+ if(results[i]){
+ urls.push(results[i]);
+ }
+ }
+ resolve(urls[0]);
+ });
+ });
+ },
+
+ stop_searching: function(){
+ this.searching_for_proxy = false;
+ this.set_connection_status('disconnected');
+ },
+
+ // this allows the client to be notified when a proxy call is made. The notification
+ // callback will be executed with the same arguments as the proxy call
+ add_notification: function(name, callback){
+ if(!this.notifications[name]){
+ this.notifications[name] = [];
+ }
+ this.notifications[name].push(callback);
+ },
+
+ /**
+ * Returns the weight on the scale.
+ *
+ * @returns {Promise<Object>}
+ */
+ scale_read: function () {
+ var self = this;
+ if (self.use_debug_weight) {
+ return Promise.resolve({weight:this.debug_weight, unit:'Kg', info:'ok'});
+ }
+ return new Promise(function (resolve, reject) {
+ self.message('scale_read',{})
+ .then(function (weight) {
+ resolve(weight);
+ }, function () { //failed to read weight
+ resolve({weight:0.0, unit:'Kg', info:'ok'});
+ });
+ });
+ },
+
+ // sets a custom weight, ignoring the proxy returned value.
+ debug_set_weight: function(kg){
+ this.use_debug_weight = true;
+ this.debug_weight = kg;
+ },
+
+ // resets the custom weight and re-enable listening to the proxy for weight values
+ debug_reset_weight: function(){
+ this.use_debug_weight = false;
+ this.debug_weight = 0;
+ },
+
+ update_customer_facing_display: function(html) {
+ if (this.posbox_supports_display) {
+ return this.message('customer_facing_display',
+ { html: html },
+ { timeout: 5000 });
+ }
+ },
+
+ /**
+ * @param {string} html
+ * @returns {Promise}
+ */
+ take_ownership_over_client_screen: function(html) {
+ return this.message("take_control", { html: html });
+ },
+
+ /**
+ * @returns {Promise}
+ */
+ test_ownership_of_client_screen: function() {
+ if (this.connection) {
+ return this.message("test_ownership", {});
+ }
+ return Promise.reject({abort: true});
+ },
+
+ // asks the proxy to log some information, as with the debug.log you can provide several arguments.
+ log: function(){
+ return this.message('log',{'arguments': _.toArray(arguments)});
+ },
+
+});
+
+return {
+ JobQueue: JobQueue,
+ ProxyDevice: ProxyDevice,
+};
+
+});