You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.7 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Hardware Customer Display module for OpenERP
  5. # Copyright (C) 2014 Akretion (http://www.akretion.com)
  6. # @author Alexis de Lattre <alexis.delattre@akretion.com>
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ##############################################################################
  22. import logging
  23. import simplejson
  24. import time
  25. from threading import Thread, Lock
  26. from Queue import Queue
  27. from unidecode import unidecode
  28. from serial import Serial
  29. import openerp.addons.hw_proxy.controllers.main as hw_proxy
  30. from openerp import http
  31. from openerp.tools.config import config
  32. logger = logging.getLogger(__name__)
  33. class CustomerDisplayDriver(Thread):
  34. def __init__(self):
  35. Thread.__init__(self)
  36. self.queue = Queue()
  37. self.lock = Lock()
  38. self.status = {'status': 'connecting', 'messages': []}
  39. self.device_name = config.get(
  40. 'customer_display_device_name', '/dev/ttyUSB0')
  41. self.device_rate = int(config.get(
  42. 'customer_display_device_rate', 9600))
  43. self.device_timeout = int(config.get(
  44. 'customer_display_device_timeout', 2))
  45. self.device_rows = int(config.get(
  46. 'customer_display_device_rows', 2))
  47. self.device_cols = int(config.get(
  48. 'customer_display_device_cols', 20))
  49. self.serial = False
  50. def get_status(self):
  51. self.push_task('status')
  52. return self.status
  53. def set_status(self, status, message=None):
  54. if status == self.status['status']:
  55. if message is not None and message != self.status['messages'][-1]:
  56. self.status['messages'].append(message)
  57. else:
  58. self.status['status'] = status
  59. if message:
  60. self.status['messages'] = [message]
  61. else:
  62. self.status['messages'] = []
  63. if status == 'error' and message:
  64. logger.error('Display Error: '+message)
  65. elif status == 'disconnected' and message:
  66. logger.warning('Disconnected Display: '+message)
  67. def lockedstart(self):
  68. with self.lock:
  69. if not self.isAlive():
  70. self.daemon = True
  71. self.start()
  72. def push_task(self, task, data=None):
  73. self.lockedstart()
  74. self.queue.put((time.time(), task, data))
  75. def move_cursor(self, col, row):
  76. # Bixolon spec : 11. "Move Cursor to Specified Position"
  77. self.cmd_serial_write('\x1B\x6C' + chr(col) + chr(row))
  78. def display_text(self, lines):
  79. logger.debug(
  80. "Preparing to send the following lines to LCD: %s" % lines)
  81. if len(lines) > self.device_rows:
  82. logger.error(
  83. 'Odoo POS sends %d rows but LCD only has %d rows'
  84. % (len(lines), self.device_rows))
  85. return
  86. assert len(lines) <= self.device_rows, 'Too many lines'
  87. lines_ascii = []
  88. for line in lines:
  89. lines_ascii.append(unidecode(line))
  90. row = 0
  91. for dline in lines_ascii:
  92. row += 1
  93. self.move_cursor(1, row)
  94. if len(line) > self.device_cols:
  95. logger.error(
  96. 'Odoo POS sends %d characters but LCD only has %d cols'
  97. % (len(line), self.device_cols))
  98. return
  99. self.serial_write(dline)
  100. def setup_customer_display(self):
  101. '''Set LCD cursor to off
  102. If your LCD has different setup instruction(s), you should
  103. inherit this function'''
  104. # Bixolon spec : 35. "Set Cursor On/Off"
  105. self.cmd_serial_write('\x1F\x43\x00')
  106. logger.debug('LCD cursor set to off')
  107. def clear_customer_display(self):
  108. '''If your LCD has different clearing instruction, you should inherit
  109. this function'''
  110. # Bixolon spec : 12. "Clear Display Screen and Clear String Mode"
  111. self.cmd_serial_write('\x0C')
  112. logger.debug('Customer display cleared')
  113. def cmd_serial_write(self, command):
  114. '''If your LCD requires a prefix and/or suffix on all commands,
  115. you should inherit this function'''
  116. assert isinstance(command, str), 'command must be a string'
  117. self.serial_write(command)
  118. def serial_write(self, text):
  119. assert isinstance(text, str), 'text must be a string'
  120. self.serial.write(text)
  121. def send_text_customer_display(self, text_to_display):
  122. lines = simplejson.loads(text_to_display)
  123. assert isinstance(lines, list), 'lines_list should be a list'
  124. try:
  125. logger.debug(
  126. 'Opening serial port %s for customer display with baudrate %d'
  127. % (self.device_name, self.device_rate))
  128. self.serial = Serial(
  129. self.device_name, self.device_rate,
  130. timeout=self.device_timeout)
  131. logger.debug('serial.is_open = %s' % self.serial.isOpen())
  132. self.setup_customer_display()
  133. self.clear_customer_display()
  134. self.display_text(lines)
  135. except Exception, e:
  136. logger.error('Exception in serial connection: %s' % str(e))
  137. finally:
  138. if self.serial:
  139. logger.debug('Closing serial port for customer display')
  140. self.serial.close()
  141. def run(self):
  142. while True:
  143. try:
  144. timestamp, task, data = self.queue.get(True)
  145. if task == 'display':
  146. self.send_text_customer_display(data)
  147. elif task == 'status':
  148. pass
  149. except Exception as e:
  150. self.set_status('error', str(e))
  151. driver = CustomerDisplayDriver()
  152. hw_proxy.drivers['customer_display'] = driver
  153. class CustomerDisplayProxy(hw_proxy.Proxy):
  154. @http.route(
  155. '/hw_proxy/send_text_customer_display', type='json', auth='none',
  156. cors='*')
  157. def send_text_customer_display(self, text_to_display):
  158. logger.debug('LCD: Call send_text_customer_display')
  159. driver.push_task('display', text_to_display)