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.

238 lines
9.7 KiB

10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. # © 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. from odoo import models, fields, api, _
  5. from odoo.exceptions import UserError, ValidationError
  6. from pprint import pformat
  7. import logging
  8. _logger = logging.getLogger(__name__)
  9. try:
  10. # pip install py-Asterisk
  11. from Asterisk import Manager
  12. except ImportError:
  13. _logger.debug('Cannot import Asterisk')
  14. Manager = None
  15. class AsteriskServer(models.Model):
  16. '''Asterisk server object, stores the parameters of the Asterisk IPBXs'''
  17. _name = "asterisk.server"
  18. _description = "Asterisk Servers"
  19. name = fields.Char(string='Asterisk Server Name', required=True)
  20. active = fields.Boolean(
  21. string='Active', default=True)
  22. ip_address = fields.Char(
  23. string='Asterisk IP address or DNS', required=True)
  24. port = fields.Integer(
  25. string='Port', required=True, default=5038,
  26. help="TCP port on which the Asterisk Manager Interface listens. "
  27. "Defined in /etc/asterisk/manager.conf on Asterisk.")
  28. out_prefix = fields.Char(
  29. string='Out Prefix', size=4, help="Prefix to dial to make outgoing "
  30. "calls. If you don't use a prefix to make outgoing calls, "
  31. "leave empty.")
  32. login = fields.Char(
  33. string='AMI Login', required=True,
  34. help="Login that Odoo will use to communicate with the "
  35. "Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
  36. "on your Asterisk server.")
  37. password = fields.Char(
  38. string='AMI Password', required=True,
  39. help="Password that Odoo will use to communicate with the "
  40. "Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
  41. "on your Asterisk server.")
  42. context = fields.Char(
  43. string='Dialplan Context', required=True,
  44. help="Asterisk dialplan context from which the calls will be "
  45. "made. Refer to /etc/asterisk/extensions.conf on your Asterisk "
  46. "server.")
  47. wait_time = fields.Integer(
  48. string='Wait Time', required=True, default=15,
  49. help="Amount of time (in seconds) Asterisk will try to reach "
  50. "the user's phone before hanging up.")
  51. extension_priority = fields.Integer(
  52. string='Extension Priority', required=True, default=1,
  53. help="Priority of the extension in the Asterisk dialplan. Refer "
  54. "to /etc/asterisk/extensions.conf on your Asterisk server.")
  55. alert_info = fields.Char(
  56. string='Alert-Info SIP Header',
  57. help="Set Alert-Info header in SIP request to user's IP Phone "
  58. "for the click2dial feature. If empty, the Alert-Info header "
  59. "will not be added. You can use it to have a special ring tone "
  60. "for click2dial (a silent one !) or to activate auto-answer "
  61. "for example.")
  62. company_id = fields.Many2one(
  63. 'res.company', string='Company',
  64. default=lambda self: self.env['res.company']._company_default_get(
  65. 'asterisk.server'),
  66. help="Company who uses the Asterisk server.")
  67. @api.multi
  68. @api.constrains(
  69. 'out_prefix', 'wait_time', 'extension_priority', 'port',
  70. 'context', 'alert_info', 'login', 'password')
  71. def _check_validity(self):
  72. for server in self:
  73. out_prefix = ('Out prefix', server.out_prefix)
  74. dialplan_context = ('Dialplan context', server.context)
  75. alert_info = ('Alert-Info SIP header', server.alert_info)
  76. login = ('AMI login', server.login)
  77. password = ('AMI password', server.password)
  78. if out_prefix[1] and not out_prefix[1].isdigit():
  79. raise ValidationError(
  80. _("Only use digits for the '%s' on the Asterisk server "
  81. "'%s'" % (out_prefix[0], server.name)))
  82. if server.wait_time < 1 or server.wait_time > 120:
  83. raise ValidationError(
  84. _("You should set a 'Wait time' value between 1 and 120 "
  85. "seconds for the Asterisk server '%s'" % server.name))
  86. if server.extension_priority < 1:
  87. raise ValidationError(
  88. _("The 'extension priority' must be a positive value for "
  89. "the Asterisk server '%s'" % server.name))
  90. if server.port > 65535 or server.port < 1:
  91. raise ValidationError(
  92. _("You should set a TCP port between 1 and 65535 for the "
  93. "Asterisk server '%s'" % server.name))
  94. for check_str in [dialplan_context, alert_info, login, password]:
  95. if check_str[1]:
  96. try:
  97. check_str[1].encode('ascii')
  98. except UnicodeEncodeError:
  99. raise ValidationError(
  100. _("The '%s' should only have ASCII caracters for "
  101. "the Asterisk server '%s'"
  102. % (check_str[0], server.name)))
  103. @api.model
  104. def _connect_to_asterisk(self):
  105. '''
  106. Open the connection to the Asterisk Manager
  107. Returns an instance of the Asterisk Manager
  108. '''
  109. user = self.env.user
  110. ast_server = user.get_asterisk_server_from_user()
  111. # We check if the current user has a chan type
  112. if not user.asterisk_chan_type:
  113. raise UserError(
  114. _('No channel type configured for the current user.'))
  115. # We check if the current user has an internal number
  116. if not user.resource:
  117. raise UserError(
  118. _('No resource name configured for the current user'))
  119. _logger.debug(
  120. "User's phone: %s/%s", user.asterisk_chan_type, user.resource)
  121. _logger.debug(
  122. "Asterisk server: %s:%d", ast_server.ip_address, ast_server.port)
  123. # Connect to the Asterisk Manager Interface
  124. try:
  125. ast_manager = Manager.Manager(
  126. (ast_server.ip_address, ast_server.port),
  127. ast_server.login, ast_server.password)
  128. except Exception, e:
  129. _logger.error(
  130. "Error in the request to the Asterisk Manager Interface %s",
  131. ast_server.ip_address)
  132. _logger.error("Here is the error message: %s", e)
  133. raise UserError(
  134. _("Problem in the request from Odoo to Asterisk. "
  135. "Here is the error message: %s" % e))
  136. return (user, ast_server, ast_manager)
  137. @api.multi
  138. def test_ami_connection(self):
  139. self.ensure_one()
  140. ast_manager = False
  141. try:
  142. ast_manager = Manager.Manager(
  143. (self.ip_address, self.port),
  144. self.login,
  145. self.password)
  146. except Exception, e:
  147. raise UserError(
  148. _("Connection Test Failed! The error message is: %s" % e))
  149. finally:
  150. if ast_manager:
  151. ast_manager.Logoff()
  152. raise UserError(_(
  153. "Connection Test Successfull! Odoo can successfully login to "
  154. "the Asterisk Manager Interface."))
  155. @api.model
  156. def _get_calling_number_from_channel(self, chan, user):
  157. '''Method designed to be inherited to work with
  158. very old or very new versions of Asterisk'''
  159. sip_account = user.asterisk_chan_type + '/' + user.resource
  160. internal_number = user.internal_number
  161. # 4 = Ring
  162. # 6 = Up
  163. if (
  164. chan.get('ChannelState') in ('4', '6') and (
  165. chan.get('ConnectedLineNum') == internal_number or
  166. chan.get('EffectiveConnectedLineNum') == internal_number or
  167. sip_account in chan.get('BridgedChannel', ''))):
  168. _logger.debug(
  169. "Found a matching Event with channelstate = %s",
  170. chan.get('ChannelState'))
  171. return chan.get('CallerIDNum')
  172. # Compatibility with Asterisk 1.4
  173. if (
  174. chan.get('State') == 'Up' and
  175. sip_account in chan.get('Link', '')):
  176. _logger.debug("Found a matching Event in 'Up' state")
  177. return chan.get('CallerIDNum')
  178. return False
  179. @api.model
  180. def _get_calling_number(self):
  181. user, ast_server, ast_manager = self._connect_to_asterisk()
  182. calling_party_number = False
  183. try:
  184. list_chan = ast_manager.Status()
  185. # from pprint import pprint
  186. # pprint(list_chan)
  187. _logger.debug("Result of Status AMI request:")
  188. _logger.debug(pformat(list_chan))
  189. for chan in list_chan.values():
  190. calling_party_number = self._get_calling_number_from_channel(
  191. chan, user)
  192. if calling_party_number:
  193. break
  194. except Exception, e:
  195. _logger.error(
  196. "Error in the Status request to Asterisk server %s",
  197. ast_server.ip_address)
  198. _logger.error(
  199. "Here are the details of the error: '%s'", unicode(e))
  200. raise UserError(
  201. _("Can't get calling number from Asterisk.\nHere is the "
  202. "error: '%s'" % unicode(e)))
  203. finally:
  204. ast_manager.Logoff()
  205. _logger.debug("Calling party number: '%s'", calling_party_number)
  206. return calling_party_number
  207. @api.model
  208. def get_record_from_my_channel(self):
  209. calling_number = self.env['asterisk.server']._get_calling_number()
  210. # calling_number = "0641981246"
  211. if calling_number:
  212. record = self.env['phone.common'].get_record_from_phone_number(
  213. calling_number)
  214. if record:
  215. return record
  216. else:
  217. return calling_number
  218. else:
  219. return False