diff --git a/README.md b/README.md
index a7e78ec..ef5c6f8 100644
--- a/README.md
+++ b/README.md
@@ -5,18 +5,6 @@ Odoo PosBox in Docker. It additionally contains
[pos-addons](https://github.com/it-projects-llc/pos-addons)
for using receipt printers over the network.
-## Running on Raspberry Pi
-
-The official Docker images of Odoo are not multi-arch
-compatible, the image needs to be rebuilt on arm.
-
-1. Change the `FROM` in the [Dockerfile](https://github.com/odoo/docker/blob/master/11.0/Dockerfile) to
-`FROM armhf/debian:stretch` and build it as `local/odoo:11`.
-1. Change the `FROM` in [odoo/Dockerfile](odoo/Dockerfile) to
- `local/odoo:11`.
-
-Corresponding issue on GitHub: [Support for ARM64v8 #195](https://github.com/odoo/docker/issues/195).
-
## Backup configuration
Example contents of `backup.env`:
diff --git a/docker-compose.yml b/docker-compose.yml
index 0d281a6..fb8c958 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,12 +8,16 @@ services:
- 80:8069
volumes:
- odoo-web-data:/var/lib/odoo
+ environment:
+ - HOST=db
+ - USER=odoo
+ - PASSWORD=odoo
networks:
localnet:
ipv4_address: 10.5.0.2
- posbox:
- build: ./posbox
- command: -- --load=web,hw_proxy,hw_posbox_homepage,hw_escpos,hw_screen,hw_printer_network
+ iotbox:
+ build: ./iotbox
+ command: -- --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be,hw_screen,hw_drivers,hw_printer_network
ports:
- 8070:8069
- 8072:8072
@@ -23,8 +27,9 @@ services:
localnet:
ipv4_address: 10.5.0.3
db:
- image: postgres:9.6
+ image: postgres:10
environment:
+ - POSTGRES_DB=postgres
- POSTGRES_PASSWORD=odoo
- POSTGRES_USER=odoo
- PGDATA=/var/lib/postgresql/data/pgdata
@@ -33,28 +38,28 @@ services:
networks:
localnet:
ipv4_address: 10.5.0.4
- backup:
- build: ./backup
- environment:
- - PGHOST=db
- - PGUSER=odoo
- - PGPASSWORD=odoo
- - BACKUP_SCHEDULE=0 19 * * *
- env_file: backup.env
- volumes:
- - odoo-db-data:/data/pg_raw:ro
- - /home/pi:/data/home-pi:ro
- networks:
- localnet:
- ipv4_address: 10.5.0.5
- monitoring:
- build: ./monitoring
- command: checkup every 10m
- volumes:
- - ./checkup.json:/opt/checkup/checkup.json:ro
- networks:
- localnet:
- ipv4_address: 10.5.0.6
+# backup:
+# build: ./backup
+# environment:
+# - PGHOST=db
+# - PGUSER=odoo
+# - PGPASSWORD=odoo
+# - BACKUP_SCHEDULE=0 19 * * *
+# env_file: backup.env
+# volumes:
+# - odoo-db-data:/data/pg_raw:ro
+# - /home/pi:/data/home-pi:ro
+# networks:
+# localnet:
+# ipv4_address: 10.5.0.5
+# monitoring:
+# build: ./monitoring
+# command: checkup every 10m
+# volumes:
+# - ./checkup.json:/opt/checkup/checkup.json:ro
+# networks:
+# localnet:
+# ipv4_address: 10.5.0.6
volumes:
odoo-web-data:
odoo-db-data:
diff --git a/iotbox/Dockerfile b/iotbox/Dockerfile
new file mode 100644
index 0000000..cb65350
--- /dev/null
+++ b/iotbox/Dockerfile
@@ -0,0 +1,78 @@
+FROM odoo:12
+
+USER root
+
+## Dependencies for iotbox
+## See also https://github.com/odoo/odoo/blob/master/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
+RUN set -x; apt-get update \
+ && apt-get -y install --no-install-recommends \
+ bluez \
+ cups \
+ cups-ipp-utils \
+ dbus \
+ gcc \
+ git \
+ libcups2-dev \
+ python3-babel \
+ python3-dateutil \
+ python3-dbus \
+ python3-decorator \
+ python3-dev \
+ python3-docutils \
+ python3-feedparser \
+ python3-gi \
+ python3-html2text \
+ python3-jinja2 \
+ python3-ldap3 \
+ python3-libsass \
+ python3-lxml \
+ python3-mako \
+ python3-mock \
+ python3-netifaces \
+ python3-openid \
+ python3-passlib \
+ python3-pil \
+ python3-pip \
+ python3-psutil \
+ python3-psycopg2 \
+ python3-pydot \
+ python3-pyparsing \
+ python3-pypdf2 \
+ python3-pyscard \
+ python3-qrcode \
+ python3-reportlab \
+ python3-requests \
+ python3-serial \
+ python3-simplejson \
+ python3-simplejson \
+ python3-tz \
+ python3-unittest2 \
+ python3-urllib3 \
+ python3-vatnumber \
+ python3-werkzeug \
+ python3-wheel \
+ && rm -rf /var/lib/apt/lists/* \
+ && mkdir -p /opt/posbox/addons \
+ && chown -R odoo.odoo /opt/posbox \
+ && mkdir /usr/lib/python3/dist-packages/odoo/addons/hw_drivers/drivers
+
+RUN pip3 install \
+ pyusb==1.0.0b1 \
+ evdev \
+ gatt \
+ v4l2 \
+ polib \
+ pycups
+
+# See https://bugs.launchpad.net/python-v4l2/+bug/1664158
+COPY python-v4l2-1664158-fix.patch /tmp
+RUN patch -p0 /usr/local/lib/python3.5/dist-packages/v4l2.py < /tmp/python-v4l2-1664158-fix.patch
+
+#USER odoo
+
+## Get pos-addons for pos_printer_network
+RUN git clone --depth=1 -b 12.0 https://github.com/it-projects-llc/pos-addons.git \
+ /opt/posbox/addons
+
+COPY odoo.conf /etc/odoo/odoo.conf
+COPY entrypoint.sh /
diff --git a/iotbox/bootstrap.min.js b/iotbox/bootstrap.min.js
new file mode 100644
index 0000000..7c1561a
--- /dev/null
+++ b/iotbox/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v3.2.0 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('
').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;e?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(150):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var f=function(){c.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",f).emulateTransitionEnd(150):f()}else b&&b()},c.prototype.checkScrollbar=function(){document.body.clientWidth>=window.innerWidth||(this.scrollbarWidth=this.scrollbarWidth||this.measureScrollbar())},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.2.0",c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var c=a.contains(document.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!c)return;var d=this,e=this.tip(),f=this.getUID(this.type);this.setContent(),e.attr("id",f),this.$element.attr("aria-describedby",f),this.options.animation&&e.addClass("fade");var g="function"==typeof this.options.placement?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,i=h.test(g);i&&(g=g.replace(h,"")||"top"),e.detach().css({top:0,left:0,display:"block"}).addClass(g).data("bs."+this.type,this),this.options.container?e.appendTo(this.options.container):e.insertAfter(this.$element);var j=this.getPosition(),k=e[0].offsetWidth,l=e[0].offsetHeight;if(i){var m=g,n=this.$element.parent(),o=this.getPosition(n);g="bottom"==g&&j.top+j.height+l-o.scroll>o.height?"top":"top"==g&&j.top-o.scroll-l<0?"bottom":"right"==g&&j.right+k>o.width?"left":"left"==g&&j.left-kg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){clearTimeout(this.timeout),this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.2.0",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").empty()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.2.0",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.2.0",c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.closest("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},c.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one("bsTransitionEnd",e).emulateTransitionEnd(150):e(),f.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(c){c.preventDefault(),b.call(a(this),"show")})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.2.0",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=a(document).height(),d=this.$target.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=b-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){null!=this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:b-this.$element.height()-h}))}}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},d.offsetBottom&&(d.offset.bottom=d.offsetBottom),d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
\ No newline at end of file
diff --git a/posbox/entrypoint.sh b/iotbox/entrypoint.sh
similarity index 77%
rename from posbox/entrypoint.sh
rename to iotbox/entrypoint.sh
index a958c3c..4cf9cc4 100755
--- a/posbox/entrypoint.sh
+++ b/iotbox/entrypoint.sh
@@ -13,7 +13,7 @@ DB_ARGS=()
function check_config() {
param="$1"
value="$2"
- if ! grep -q -E "^\s*\b${param}\b\s*=" "$OPENERP_SERVER" ; then
+ if ! grep -q -E "^\s*\b${param}\b\s*=" "$ODOO_RC" ; then
DB_ARGS+=("--${param}")
DB_ARGS+=("${value}")
fi;
@@ -24,16 +24,18 @@ check_config "db_user" "$USER"
check_config "db_password" "$PASSWORD"
case "$1" in
- -- | openerp-server)
+ -- | odoo)
shift
+ /etc/init.d/cups start
+ /etc/init.d/dbus start
if [[ "$1" == "scaffold" ]] ; then
- exec openerp-server "$@"
+ exec odoo "$@"
else
- exec openerp-server "$@" "${DB_ARGS[@]}"
+ exec odoo "$@" "${DB_ARGS[@]}"
fi
;;
-*)
- exec openerp-server "$@" "${DB_ARGS[@]}"
+ exec odoo "$@" "${DB_ARGS[@]}"
;;
*)
exec "$@"
diff --git a/iotbox/odoo.conf b/iotbox/odoo.conf
new file mode 100644
index 0000000..b28119f
--- /dev/null
+++ b/iotbox/odoo.conf
@@ -0,0 +1,4 @@
+[options]
+addons_path = /mnt/extra-addons,/opt/posbox/addons
+data_dir = /var/lib/odoo
+admin_passwd = S3Cur3Passw0rd
diff --git a/iotbox/python-v4l2-1664158-fix.patch b/iotbox/python-v4l2-1664158-fix.patch
new file mode 100644
index 0000000..d3f11fa
--- /dev/null
+++ b/iotbox/python-v4l2-1664158-fix.patch
@@ -0,0 +1,22 @@
+=== modified file 'v4l2.py'
+--- v4l2.py 2010-07-22 01:07:58 +0000
++++ v4l2.py 2018-03-03 01:59:16 +0000
+@@ -194,7 +194,7 @@
+ V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
+ V4L2_BUF_TYPE_PRIVATE,
+-) = range(1, 9) + [0x80]
++) = list(range(1, 9)) + [0x80]
+
+
+ v4l2_ctrl_type = enum
+@@ -245,7 +245,7 @@
+ V4L2_PRIORITY_INTERACTIVE,
+ V4L2_PRIORITY_RECORD,
+ V4L2_PRIORITY_DEFAULT,
+-) = range(0, 4) + [2]
++) = list(range(0, 4)) + [2]
+
+
+ class v4l2_rect(ctypes.Structure):
+
diff --git a/odoo/Dockerfile b/odoo/Dockerfile
index 2950869..6a2c738 100644
--- a/odoo/Dockerfile
+++ b/odoo/Dockerfile
@@ -1,4 +1,4 @@
-FROM local/odoo:11
+FROM odoo:12
USER root
@@ -11,8 +11,8 @@ RUN apt-get update \
USER odoo
## Get pos-addons for pos_printer_network
-RUN git clone --depth=1 -b 11.0 https://github.com/it-projects-llc/pos-addons.git \
+RUN git clone --depth=1 -b 12.0 https://github.com/it-projects-llc/pos-addons.git \
/opt/posbox/addons
COPY odoo.conf /etc/odoo/odoo.conf
-COPY bootstrap.min.js /usr/lib/python3/dist-packages/odoo/addons/web/static/lib/bootstrap/js/
+#COPY bootstrap.min.js /usr/lib/python3/dist-packages/odoo/addons/web/static/lib/bootstrap/js/
diff --git a/posbox/Dockerfile b/posbox/Dockerfile
deleted file mode 100644
index 7a2d0c7..0000000
--- a/posbox/Dockerfile
+++ /dev/null
@@ -1,66 +0,0 @@
-FROM debian:jessie
-MAINTAINER Odoo S.A.
-
-# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf
-RUN set -x; \
- apt-get update \
- && apt-get install -y --no-install-recommends \
- ca-certificates \
- curl \
- git \
- node-clean-css \
- node-less \
- python-gevent \
- python-netifaces \
- python-pip \
- python-pyinotify \
- python-renderpm \
- python-support \
- && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false -o APT::AutoRemove::SuggestsImportant=false npm \
- && rm -rf /var/lib/apt/lists/* wkhtmltox.deb \
- && pip install psycogreen==1.0
-
-# Install Odoo
-ENV ODOO_VERSION 8.0
-ENV ODOO_RELEASE 20170815
-RUN set -x; \
- curl -o odoo.deb -SL http://nightly.odoo.com/${ODOO_VERSION}/nightly/deb/odoo_${ODOO_VERSION}.${ODOO_RELEASE}_all.deb \
- && echo '5835e966a07e5684b4f7bcc39585276b0bb68254 odoo.deb' | sha1sum -c - \
- && dpkg --force-depends -i odoo.deb \
- && apt-get update \
- && apt-get -y install -f --no-install-recommends \
- && rm -rf /var/lib/apt/lists/* odoo.deb
-
-# Copy entrypoint script and Odoo configuration file
-COPY ./entrypoint.sh /
-COPY ./openerp-server.conf /etc/odoo/
-RUN chown odoo /etc/odoo/openerp-server.conf
-
-# Mount /var/lib/odoo to allow restoring filestore and /mnt/extra-addons for users addons
-RUN mkdir -p /mnt/extra-addons \
- && chown -R odoo /mnt/extra-addons
-VOLUME ["/var/lib/odoo", "/mnt/extra-addons"]
-
-# Expose Odoo services
-EXPOSE 8069 8071
-
-# Set the default config file
-ENV OPENERP_SERVER /etc/odoo/openerp-server.conf
-
-## Prepare for pos-addons / hw_printer_network
-RUN pip install -U pip setuptools \
- && pip install python-escpos
-
-# hw_printer_network is located in 11.0 branch, but works with earler versions too
-RUN git clone --depth=1 -b 11.0 https://github.com/it-projects-llc/pos-addons.git \
- /opt/posbox/addons
-
-COPY hw_screen_main.py /usr/lib/python3/dist-packages/odoo/addons/hw_screen/controllers/main.py
-COPY hw_screen_main.py /usr/lib/python2.7/dist-packages/openerp/addons/hw_screen/controllers/main.py
-COPY hw_printer_network_controller.py /opt/posbox/addons/hw_printer_network/controllers/hw_printer_network_controller.py
-
-# Set default user when running the container
-USER odoo
-
-ENTRYPOINT ["/entrypoint.sh"]
-CMD ["openerp-server"]
diff --git a/posbox/hw_printer_network_controller.py b/posbox/hw_printer_network_controller.py
deleted file mode 100644
index 6bb8b94..0000000
--- a/posbox/hw_printer_network_controller.py
+++ /dev/null
@@ -1,209 +0,0 @@
-# Copyright 2017-2018 Dinar Gabbasov
-# Copyright 2018 Tom Blauwendraat
-# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-
-from openerp import http
-import logging
-import time
-import socket
-import subprocess
-import threading
-import traceback
-
-
-_logger = logging.getLogger(__name__)
-
-
-try:
- from openerp.addons.hw_escpos.escpos import escpos
- from openerp.addons.hw_escpos.controllers.main import EscposProxy
- from openerp.addons.hw_escpos.controllers.main import EscposDriver
- from openerp.addons.hw_escpos.escpos.printer import Network
- import openerp.addons.hw_proxy.controllers.main as hw_proxy
-except ImportError:
- EscposProxy = object
- EscposDriver = object
-
-
-class PingProcess(threading.Thread):
- def __init__(self, ip):
- self.stdout = None
- self.stderr = None
- threading.Thread.__init__(self)
- self.status = 'offline'
- self.ip = ip
- self.stop = False
-
- def run(self):
- while not self.stop:
- child = subprocess.Popen(
- ["ping", "-c1", "-w5", self.ip],
- shell=False,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- child.communicate()
- self.status = 'offline' if child.returncode else 'online'
- time.sleep(1)
-
- def get_status(self):
- return self.status
-
- def __del__(self):
- self.stop = True
-
-
-class EscposNetworkDriver(EscposDriver):
-
- def __init__(self):
- self.network_printers = []
- self.ping_processes = {}
- self.printer_objects = {}
- super(EscposNetworkDriver, self).__init__()
-
- def get_network_printer(self, ip, name=None):
- found_printer = False
- for printer in self.network_printers:
- if printer['ip'] == ip:
- found_printer = True
- if name:
- printer['name'] = name
- if printer['status'] == 'online':
- printer_object = self.printer_objects.get(ip, None)
- if not printer_object:
- try:
- printer_object = Network(ip)
- self.printer_objects[ip] = printer_object
- except socket.error:
- pass
- return printer
- if not found_printer:
- self.add_network_printer(ip, name)
- return None
-
- def add_network_printer(self, ip, name=None):
- printer = dict(
- ip=ip,
- status='offline',
- name=name or 'Unnamed printer'
- )
- self.network_printers.append(printer) # dont return because offline
- self.start_pinging(ip)
-
- def start_pinging(self, ip):
- pinger = PingProcess(ip)
- self.ping_processes[ip] = pinger
- pinger.start()
-
- def update_driver_status(self):
- count = len([p for p in self.network_printers if p.get('status', None) == 'online'])
- if count:
- self.set_status('connected', '{} printer(s) Connected'.format(count))
- else:
- self.set_status('disconnected', 'Disconnected')
-
- def run(self):
- if not escpos:
- _logger.error('ESC/POS cannot initialize, please verify system dependencies.')
- return
- while True:
- try:
- error = True
- timestamp, task, data = self.queue.get(True)
- if task == 'xml_receipt':
- error = False
- if timestamp >= (time.time() - 1 * 60 * 60):
- receipt, network_printer_ip = data
- printer_info = self.get_network_printer(network_printer_ip)
- printer = self.printer_objects.get(network_printer_ip, None)
- if printer_info and printer_info['status'] == 'online' and printer:
- _logger.info('Printing XML receipt on printer %s...', network_printer_ip)
- try:
- printer.receipt(receipt)
- except socket.error:
- printer.open()
- printer.receipt(receipt)
- _logger.info('Done printing XML receipt on printer %s', network_printer_ip)
- else:
- _logger.error('xml_receipt: printer offline!')
- # add a missed order to queue
- time.sleep(3)
- self.queue.put((timestamp, task, data))
- elif task == 'cashbox':
- printer_info = self.get_network_printer(data)
- printer = self.printer_objects.get(data, None)
- if printer_info and printer_info['status'] == 'online' and printer:
- _logger.info('Opening cashbox on printer %s...', data)
- try:
- printer.cashdraw(2)
- printer.cashdraw(5)
- except socket.error:
- printer.open()
- printer.cashdraw(2)
- printer.cashdraw(5)
- _logger.info('Done opening cashbox on printer %s', data)
- else:
- _logger.error('cashbox: printer offline!')
- elif task == 'printstatus':
- pass
- elif task == 'status':
- error = False
- for printer in self.network_printers:
- ip = printer['ip']
- pinger = self.ping_processes.get(ip, None)
- if pinger and pinger.isAlive():
- status = pinger.get_status()
- if status != printer['status']:
- # todo: use a lock?
- printer['status'] = status
- self.update_driver_status()
- else:
- self.start_pinging(ip)
- error = False
- except Exception as e:
- self.set_status('error', str(e))
- errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
- _logger.error(errmsg)
- finally:
- if error:
- self.queue.put((timestamp, task, data))
-
-
-# Separate instance, mainloop and queue for network printers
-# original driver runs in parallel and deals with USB printers
-network_driver = EscposNetworkDriver()
-
-hw_proxy.drivers['escpos_network'] = network_driver
-
-# this will also start the message handling loop
-network_driver.push_task('printstatus')
-
-
-class UpdatedEscposProxy(EscposProxy):
- @http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*')
- def print_xml_receipt(self, receipt, proxy=None):
- if proxy:
- _logger.info('print_xml_receipt proxy %s', proxy)
- network_driver.push_task('xml_receipt', (receipt, proxy))
- else:
- super(UpdatedEscposProxy, self).print_xml_receipt(receipt)
-
- @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
- def open_cashbox(self, proxy=None):
- _logger.info('Open cashbox via network printer')
- network_driver.push_task('cashbox', '192.168.233.3')
-
- @http.route('/hw_proxy/network_printers', type='json', auth='none', cors='*')
- def network_printers(self, network_printers=None):
- for printer in network_printers:
- network_driver.get_network_printer(printer['ip'], name=printer['name'])
-
- @http.route('/hw_proxy/status_network_printers', type='json', auth='none', cors='*')
- def network_printers_status(self):
- return network_driver.network_printers
-
- @http.route('/hw_proxy/without_usb', type='http', auth='none', cors='*')
- def without_usb(self):
- """ Old pos_printer_network module expects this to work """
- return "ping"
-
diff --git a/posbox/hw_screen_main.py b/posbox/hw_screen_main.py
deleted file mode 100644
index 9cf144a..0000000
--- a/posbox/hw_screen_main.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2015 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-
-from openerp import http
-from openerp.tools import config
-from openerp.addons.web.controllers import main as web
-from openerp.addons.hw_posbox_homepage.controllers import main as homepage
-
-import logging
-import netifaces as ni
-import os
-from subprocess import call
-import time
-import threading
-
-self_port = str(config['xmlrpc_port'] or 8069)
-
-_logger = logging.getLogger(__name__)
-
-class Homepage(homepage.PosboxHomepage):
-
- def get_hw_screen_message(self):
- return """
-
-If you need to display the current customer basket on another device, you can do it here.
-
-"""
-
-class HardwareScreen(web.Home):
-
- event_data = threading.Event()
- pos_client_data = {'rendered_html': False,
- 'ip_from': False}
- display_in_use = ''
- failure_count = {}
-
- def _call_xdotools(self, keystroke):
- os.environ['DISPLAY'] = ":0.0"
- os.environ['XAUTHORITY'] = "/run/lightdm/pi/xauthority"
- try:
- call(['xdotool', 'key', keystroke])
- return "xdotool succeeded in stroking " + keystroke
- except:
- return "xdotool threw an error, maybe it is not installed on the posbox"
-
- @http.route('/hw_proxy/display_refresh', type='json', auth='none', cors='*')
- def display_refresh(self):
- return self._call_xdotools('F5')
-
- # POS CASHIER'S ROUTES
- @http.route('/hw_proxy/customer_facing_display', type='json', auth='none', cors='*')
- def update_user_facing_display(self, html=None):
- request_ip = http.request.httprequest.remote_addr
- if request_ip == HardwareScreen.pos_client_data.get('ip_from', ''):
- HardwareScreen.pos_client_data['rendered_html'] = html
- HardwareScreen.event_data.set()
-
- return {'status': 'updated'}
- else:
- return {'status': 'failed'}
-
- @http.route('/hw_proxy/take_control', type='json', auth='none', cors='*')
- def take_control(self, html=None):
- # ALLOW A CASHIER TO TAKE CONTROL OVER THE POSBOX, IN CASE OF MULTIPLE CASHIER PER POSBOX
- HardwareScreen.pos_client_data['rendered_html'] = html
- HardwareScreen.pos_client_data['ip_from'] = http.request.httprequest.remote_addr
- HardwareScreen.event_data.set()
-
- return {'status': 'success',
- 'message': 'You now have access to the display'}
-
- @http.route('/hw_proxy/test_ownership', type='json', auth='none', cors='*')
- def test_ownership(self):
- if HardwareScreen.pos_client_data.get('ip_from') == http.request.httprequest.remote_addr:
- return {'status': 'OWNER'}
- else:
- return {'status': 'NOWNER'}
-
- # POSBOX ROUTES (SELF)
- @http.route('/point_of_sale/display', type='http', auth='none')
- def render_main_display(self):
- return self._get_html()
-
- @http.route('/point_of_sale/get_serialized_order', type='json', auth='none')
- def get_serialized_order(self):
- request_addr = http.request.httprequest.remote_addr
- result = HardwareScreen.pos_client_data
- if HardwareScreen.display_in_use and request_addr != HardwareScreen.display_in_use:
- if not HardwareScreen.failure_count.get(request_addr):
- HardwareScreen.failure_count[request_addr] = 0
- if HardwareScreen.failure_count[request_addr] > 0:
- time.sleep(10)
- HardwareScreen.failure_count[request_addr] += 1
- return {'rendered_html': """Not Authorized. Another browser is in use to display for the client. Please refresh.
""",
- 'stop_longpolling': True,
- 'ip_from': request_addr}
-
- # IMPLEMENTATION OF LONGPOLLING
- # Times out 2 seconds before the JS request does
- if HardwareScreen.event_data.wait(28):
- HardwareScreen.event_data.clear()
- HardwareScreen.failure_count[request_addr] = 0
- return result
- return {'rendered_html': False,
- 'ip_from': HardwareScreen.pos_client_data['ip_from']}
-
- def _get_html(self):
- cust_js = None
- interfaces = ni.interfaces()
- my_ip = '127.0.0.1'
- HardwareScreen.display_in_use = http.request.httprequest.remote_addr
-
- with open(os.path.join(os.path.dirname(__file__), "../static/src/js/worker.js")) as js:
- cust_js = js.read()
-
- with open(os.path.join(os.path.dirname(__file__), "../static/src/css/cust_css.css")) as css:
- cust_css = css.read()
-
- display_ifaces = ""
- for iface_id in interfaces:
- iface_obj = ni.ifaddresses(iface_id)
- ifconfigs = iface_obj.get(ni.AF_INET, [])
- for conf in ifconfigs:
- if conf.get('addr'):
- display_ifaces += "" + iface_id + " | "
- display_ifaces += "" + conf.get('addr') + " | "
- display_ifaces += "" + conf.get('netmask') + " |
"
- # What is my external IP ?
- if iface_id != 'lo':
- my_ip = conf.get('addr')
-
- my_ip_port = my_ip + ":" + self_port
-
- html = """
-
-
-
- Odoo -- Point of Sale
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Odoo Point of Sale
-
POSBox Client display
-
My IPs
-
-
- Interface |
- IP |
- Netmask |
-
- """ + display_ifaces + """
-
-
The customer cart will be displayed here once a Point of Sale session is started.
-
Odoo version 11 or above is required.
-
-
-
-
-
- """
- return html
diff --git a/posbox/openerp-server.conf b/posbox/openerp-server.conf
deleted file mode 100644
index 2dc302d..0000000
--- a/posbox/openerp-server.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-[options]
-addons_path = /opt/posbox/addons
-data_dir = /var/lib/odoo