foxygit / infodisplay Log in
commits tags

/kiosk/addons/wifi/routes.py · 5.27 KB

raw
import re
import subprocess
import time
from flask import Blueprint, jsonify, request, abort
from auth import login_required

bp = Blueprint('wifi', __name__, url_prefix='/api/wifi')


def _wifi_iface():
    try:
        with open('/proc/net/wireless') as f:
            for line in f:
                if ':' in line:
                    iface = line.split(':')[0].strip()
                    if iface:
                        return iface
    except Exception:
        pass
    return 'wlo1'


_WIFI_IFACE = _wifi_iface()


def _wpa(*args):
    cmd = ['sudo', '-n', '/usr/sbin/wpa_cli', '-i', _WIFI_IFACE] + list(args)
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        return r.returncode, r.stdout.strip()
    except Exception as e:
        return 1, str(e)


def _wifi_status():
    rc, out = _wpa('status')
    d = {}
    if rc == 0:
        for line in out.splitlines():
            if '=' in line:
                k, v = line.split('=', 1)
                d[k.strip()] = v.strip()
    connected = d.get('wpa_state') == 'COMPLETED'
    ip = ''
    try:
        r = subprocess.run(['ip', '-4', 'addr', 'show', _WIFI_IFACE],
                           capture_output=True, text=True, timeout=5)
        m = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', r.stdout)
        if m:
            ip = m.group(1)
    except Exception:
        pass
    return {
        'connected': connected,
        'ssid': d.get('ssid', ''),
        'ip': ip,
        'signal': int(d.get('signal_level', 0) or 0),
        'iface': _WIFI_IFACE,
        'sudo_ok': rc != 1 or 'sudo' not in out,
    }


def _wifi_scan():
    _wpa('scan')
    time.sleep(2.5)
    rc, out = _wpa('scan_results')
    networks = []
    seen = set()
    for line in out.splitlines():
        if not line or line.startswith('bssid'):
            continue
        parts = line.split('\t')
        if len(parts) < 5:
            continue
        ssid = parts[4].strip()
        if not ssid or ssid in seen:
            continue
        seen.add(ssid)
        try:
            dbm = int(parts[2])
        except (ValueError, IndexError):
            dbm = -90
        flags = parts[3] if len(parts) > 3 else ''
        if 'WPA2' in flags:
            sec = 'WPA2'
        elif 'WPA' in flags:
            sec = 'WPA'
        elif 'WEP' in flags:
            sec = 'WEP'
        else:
            sec = 'open'
        networks.append({
            'ssid': ssid,
            'signal': max(0, min(100, 2 * (dbm + 100))),
            'security': sec,
        })
    networks.sort(key=lambda x: -x['signal'])
    return networks


def _wifi_saved():
    rc, out = _wpa('list_networks')
    saved = []
    for line in out.splitlines():
        parts = line.split('\t')
        if len(parts) >= 2 and parts[0].strip().isdigit():
            ssid = parts[1].strip()
            if ssid:
                flags = parts[3].strip() if len(parts) > 3 else ''
                saved.append({
                    'id': parts[0].strip(),
                    'ssid': ssid,
                    'current': '[CURRENT]' in flags,
                })
    return saved


def _wifi_connect(ssid, password):
    for net in _wifi_saved():
        if net['ssid'] == ssid:
            _wpa('remove_network', net['id'])
    rc, net_id = _wpa('add_network')
    net_id = net_id.strip()
    if not net_id.isdigit():
        return False, 'Kunde inte lägga till nätverk'
    _wpa('set_network', net_id, 'ssid', f'"{ssid}"')
    if password:
        _wpa('set_network', net_id, 'psk', f'"{password}"')
    else:
        _wpa('set_network', net_id, 'key_mgmt', 'NONE')
    _wpa('select_network', net_id)
    for _ in range(20):
        time.sleep(1)
        s = _wifi_status()
        if s['connected'] and s['ssid'] == ssid:
            _wpa('save_config')
            return True, s.get('ip', '')
    _wpa('remove_network', net_id)
    return False, 'Anslutning misslyckades (fel lösenord?)'


@bp.route('/status')
@login_required
def api_wifi_status():
    return jsonify(_wifi_status())


@bp.route('/networks')
@login_required
def api_wifi_networks():
    try:
        nets = _wifi_scan()
        known = {n['ssid'] for n in _wifi_saved()}
        for n in nets:
            n['known'] = n['ssid'] in known
        return jsonify({'networks': nets, 'iface': _WIFI_IFACE})
    except Exception as e:
        return jsonify({'networks': [], 'error': str(e)})


@bp.route('/connect', methods=['POST'])
@login_required
def api_wifi_connect():
    data = request.get_json()
    if not data or not data.get('ssid'):
        abort(400)
    ssid     = str(data['ssid'])[:64]
    password = str(data.get('password', ''))[:64]
    if '"' in ssid or '"' in password:
        return jsonify({'ok': False, 'message': 'Ogiltiga tecken i SSID/lösenord'}), 400
    ok, msg = _wifi_connect(ssid, password)
    return jsonify({'ok': ok, 'message': msg if not ok else '', 'ip': msg if ok else ''})


@bp.route('/disconnect', methods=['POST'])
@login_required
def api_wifi_disconnect():
    _wpa('disconnect')
    return '', 204


@bp.route('/saved')
@login_required
def api_wifi_saved():
    return jsonify({'saved': _wifi_saved()})


@bp.route('/saved/<net_id>', methods=['DELETE'])
@login_required
def api_wifi_remove(net_id):
    if not net_id.isdigit():
        abort(400)
    _wpa('remove_network', net_id)
    _wpa('save_config')
    return '', 204