bemade-addons/unifi_integration/models/unifi_wifi.py
2025-03-27 15:34:13 -04:00

446 lines
13 KiB
Python

# -*- coding: utf-8 -*-
# These imports will work in an Odoo environment, even if your IDE marks them as not found
# pylint: disable=import-error
from odoo import models, fields, api, _
from .unifi_common import UnifiCommonMixin
from odoo.exceptions import UserError, ValidationError
# pylint: enable=import-error
import json
import logging
from datetime import datetime
_logger = logging.getLogger(__name__)
class UnifiWifi(models.Model, UnifiCommonMixin):
"""Model for UniFi WiFi networks
This model represents WiFi networks configured in UniFi sites.
It stores configuration details such as SSID, password, security type, etc.
"""
_name = 'unifi.wifi'
_description = 'UniFi WiFi Network'
_order = 'name'
active = fields.Boolean(
string='Active',
default=True,
help='Whether this record is active in Odoo'
)
# Basic information
name = fields.Char(
string='Name',
required=True,
help='Name of the WiFi network (SSID)'
)
wifi_id = fields.Char(
string='WiFi ID',
required=True,
help='Unique identifier for this WiFi network in the UniFi system'
)
# WiFi configuration
enabled = fields.Boolean(
string='Enabled',
default=True,
help='Whether this WiFi network is enabled'
)
hidden = fields.Boolean(
string='Hidden SSID',
default=False,
help='Whether this SSID is hidden from network scans'
)
security = fields.Selection(
selection=[
('open', 'Open'),
('wep', 'WEP'),
('wpapsk', 'WPA Personal'),
('wpa2psk', 'WPA2 Personal'),
('wpapskwpa2psk', 'WPA/WPA2 Personal'),
('wpa3', 'WPA3 Personal'),
('wpa2enterprise', 'WPA2 Enterprise'),
('wpa3enterprise', 'WPA3 Enterprise'),
('radius', 'RADIUS')
],
string='Security Type',
default='wpa2psk',
help='Security protocol used by this WiFi network'
)
password = fields.Char(
string='Password',
help='Password for this WiFi network (stored securely)'
)
# Network configuration
network_id = fields.Many2one(
comodel_name='unifi.network',
string='Network',
help='Network associated with this WiFi'
)
vlan_id = fields.Many2one(
comodel_name='unifi.vlan',
string='VLAN',
help='VLAN associated with this WiFi'
)
# WiFi settings
band = fields.Selection(
selection=[
('2g', '2.4 GHz'),
('5g', '5 GHz'),
('both', '2.4 & 5 GHz')
],
string='Band',
default='both',
help='WiFi frequency band'
)
channel = fields.Integer(
string='Channel',
help='WiFi channel (0 for auto)'
)
channel_width = fields.Selection(
selection=[
('20', '20 MHz'),
('40', '40 MHz'),
('80', '80 MHz'),
('160', '160 MHz')
],
string='Channel Width',
help='WiFi channel width'
)
tx_power = fields.Selection(
selection=[
('auto', 'Auto'),
('low', 'Low'),
('medium', 'Medium'),
('high', 'High'),
('custom', 'Custom')
],
string='TX Power',
default='auto',
help='Transmission power'
)
tx_power_custom = fields.Integer(
string='Custom TX Power',
help='Custom transmission power in dBm'
)
# Guest network settings
is_guest = fields.Boolean(
string='Guest Network',
default=False,
help='Whether this is a guest WiFi network'
)
guest_policy = fields.Selection(
selection=[
('none', 'No restrictions'),
('mac', 'MAC filtering'),
('auth', 'Authentication required')
],
string='Guest Policy',
default='none',
help='Access policy for guest networks'
)
# Advanced settings
wpa_mode = fields.Selection(
selection=[
('wpa', 'WPA'),
('wpa2', 'WPA2'),
('wpa3', 'WPA3'),
('wpa/wpa2', 'WPA/WPA2'),
('wpa2/wpa3', 'WPA2/WPA3')
],
string='WPA Mode',
help='WPA mode for this network'
)
wpa_encryption = fields.Selection(
selection=[
('auto', 'Auto'),
('ccmp', 'CCMP (AES)'),
('tkip', 'TKIP'),
('tkip-ccmp', 'TKIP/CCMP')
],
string='WPA Encryption',
default='auto',
help='Encryption method used for WPA'
)
pmf_mode = fields.Selection(
selection=[
('disabled', 'Disabled'),
('optional', 'Optional'),
('required', 'Required')
],
string='PMF Mode',
default='optional',
help='Protected Management Frames mode'
)
# Tracking fields
created_at = fields.Datetime(
string='Created At',
default=fields.Datetime.now,
help='When this record was created in Odoo'
)
updated_at = fields.Datetime(
string='Updated At',
help='When this record was last updated in Odoo'
)
last_sync = fields.Datetime(
string='Last Sync',
help='When this record was last synchronized with UniFi'
)
# Raw data
raw_data = fields.Text(
string='Raw Data',
help='Raw JSON data from UniFi API'
)
raw_data_json = fields.Text(
string='Données brutes (JSON)',
compute='_compute_raw_data_json',
help='Données brutes de la configuration WiFi au format JSON formaté'
)
@api.depends('raw_data')
def _compute_raw_data_json(self):
for record in self:
record.raw_data_json = self.format_raw_data_json(record.raw_data)
# Relations
site_id = fields.Many2one(
comodel_name='unifi.site',
string='Site',
required=True,
ondelete='cascade',
help='UniFi site this WiFi network belongs to'
)
# Methods
def create_or_update_from_data(self, site, wifi_data):
"""Create or update a WiFi network from API data
Args:
site: The UniFi site record
wifi_data: The WiFi network data from the API
Returns:
record: The created or updated WiFi network record
"""
# Extract the WiFi ID
wifi_id = wifi_data.get('_id') or wifi_data.get('id')
if not wifi_id:
_logger.error("Cannot create/update WiFi network: missing ID")
return False
# Search for an existing WiFi network with this ID
existing_wifi = self.search([
('wifi_id', '=', wifi_id),
('site_id', '=', site.id)
], limit=1)
# Prepare values for creation/update
vals = {
'wifi_id': wifi_id,
'name': wifi_data.get('name', wifi_data.get('ssid', f"WiFi {wifi_id}")),
'site_id': site.id,
'enabled': wifi_data.get('enabled', True),
'hidden': wifi_data.get('hide_ssid', False),
'security': self._map_security_type(wifi_data.get('security', 'wpa2psk')),
'is_guest': wifi_data.get('is_guest', False),
'band': self._map_band(wifi_data.get('band', 'both')),
'last_sync': fields.Datetime.now(),
'raw_data': json.dumps(wifi_data)
}
# Add password if available (and not empty)
if wifi_data.get('x_passphrase') and wifi_data.get('x_passphrase') != 'null':
vals['password'] = wifi_data.get('x_passphrase')
# Map network and VLAN if available
if wifi_data.get('networkconf_id'):
network = self.env['unifi.network'].search([
('network_id', '=', wifi_data.get('networkconf_id')),
('site_id', '=', site.id)
], limit=1)
if network:
vals['network_id'] = network.id
if wifi_data.get('vlan_id'):
vlan = self.env['unifi.vlan'].search([
('vlan_id', '=', wifi_data.get('vlan_id')),
('site_id', '=', site.id)
], limit=1)
if vlan:
vals['vlan_id'] = vlan.id
# Add advanced settings if available
if 'channel' in wifi_data:
vals['channel'] = wifi_data.get('channel')
if 'channel_width' in wifi_data:
vals['channel_width'] = str(wifi_data.get('channel_width', '20'))
if 'tx_power' in wifi_data:
vals['tx_power'] = self._map_tx_power(wifi_data.get('tx_power'))
if 'tx_power_mode' in wifi_data and wifi_data.get('tx_power_mode') == 'custom':
vals['tx_power'] = 'custom'
vals['tx_power_custom'] = wifi_data.get('tx_power')
# WPA settings
if 'wpa_mode' in wifi_data:
vals['wpa_mode'] = self._map_wpa_mode(wifi_data.get('wpa_mode'))
if 'wpa_enc' in wifi_data:
vals['wpa_encryption'] = self._map_wpa_encryption(wifi_data.get('wpa_enc'))
if 'pmf_mode' in wifi_data:
vals['pmf_mode'] = self._map_pmf_mode(wifi_data.get('pmf_mode'))
if existing_wifi:
# Update existing WiFi network
vals['updated_at'] = fields.Datetime.now()
existing_wifi.write(vals)
return existing_wifi
else:
# Create new WiFi network
return self.create(vals)
def _map_security_type(self, security):
"""Map UniFi security type to model selection value
Args:
security: Security type from UniFi API
Returns:
str: Mapped security type
"""
security_map = {
'open': 'open',
'wep': 'wep',
'wpapsk': 'wpapsk',
'wpa2psk': 'wpa2psk',
'wpapskwpa2psk': 'wpapskwpa2psk',
'wpa3': 'wpa3',
'wpa2enterprise': 'wpa2enterprise',
'wpa3enterprise': 'wpa3enterprise',
'radius': 'radius'
}
return security_map.get(security, 'wpa2psk')
def _map_band(self, band):
"""Map UniFi band to model selection value
Args:
band: Band from UniFi API
Returns:
str: Mapped band
"""
band_map = {
'ng': '2g',
'2g': '2g',
'na': '5g',
'5g': '5g',
'both': 'both',
'all': 'both'
}
return band_map.get(band, 'both')
def _map_tx_power(self, tx_power):
"""Map UniFi TX power to model selection value
Args:
tx_power: TX power from UniFi API
Returns:
str: Mapped TX power
"""
# If it's already a string value that matches our selection options
if tx_power in ['auto', 'low', 'medium', 'high', 'custom']:
return tx_power
# If it's a numeric value, map it to a named level
try:
power = int(tx_power)
if power <= 10:
return 'low'
elif power <= 18:
return 'medium'
else:
return 'high'
except (ValueError, TypeError):
return 'auto'
def _map_wpa_mode(self, wpa_mode):
"""Map UniFi WPA mode to model selection value
Args:
wpa_mode: WPA mode from UniFi API
Returns:
str: Mapped WPA mode
"""
wpa_mode_map = {
'1': 'wpa',
'2': 'wpa2',
'3': 'wpa3',
'12': 'wpa/wpa2',
'23': 'wpa2/wpa3'
}
return wpa_mode_map.get(str(wpa_mode), 'wpa2')
def _map_wpa_encryption(self, wpa_enc):
"""Map UniFi WPA encryption to model selection value
Args:
wpa_enc: WPA encryption from UniFi API
Returns:
str: Mapped WPA encryption
"""
wpa_enc_map = {
'auto': 'auto',
'ccmp': 'ccmp',
'tkip': 'tkip',
'tkip-ccmp': 'tkip-ccmp',
'tkip,ccmp': 'tkip-ccmp'
}
return wpa_enc_map.get(wpa_enc, 'auto')
def _map_pmf_mode(self, pmf_mode):
"""Map UniFi PMF mode to model selection value
Args:
pmf_mode: PMF mode from UniFi API
Returns:
str: Mapped PMF mode
"""
pmf_mode_map = {
'0': 'disabled',
'1': 'optional',
'2': 'required',
'disabled': 'disabled',
'optional': 'optional',
'required': 'required'
}
return pmf_mode_map.get(str(pmf_mode), 'optional')